Introduction

In this class I will

Materials for this class are available on GitHub at https://github.com/ltierney/SIBS-WV-2019.git.

Materials for our Data Visualization and Data Technologies course are available at http://www.stat.uiowa.edu/~luke/classes/STAT4580/

Some tools I will be using:

Most of the packages are loaded by loading the tidyverse package

library(tidyverse)

Useful references:

Hadley Wickham and Garrett Grolemund (2016), R for Data Science, O'Reilly.

Claus O. Wilke (2019), Fundamentals of Data Visualization, O'Reilly.

Ask questions any time!

The R Language

R is a language for data analysis and graphics.

  • R was originally developed by Robert Gentleman and Ross Ihaka in the early 1990's for a Macintosh computer lab at U. of Auckland, New Zealand.
  • R is based on the S language developed by John Chambers and others at Bell Labs.

R is an Open Source project.

  • Since 1997 R is developed and maintained by the R-core group, with around 20 members located in maor than 10 different countries.

R is widely used in the field of statistics and beyond, especially in university environments.

  • R has become the primary framework for developing and making available new statistical methodology.
  • Many (now over 14,000) extension packages are available through CRAN or similar repositories.

Working with R

R is designed for interactive data exploration.

  • Interaction is through a read-eval-print loop (REPL).
  • This is also called a command line interface (CLI).

All computations are specified in the R language.

  • Even for simple tasks you need to know a little of the language.
  • After learning to do simple tasks you know some of the language.

The language is used to

  • prepare data for analysis;
  • specify individual analyses;
  • program repeated or similar analyses;
  • program new methods of analysis.

Specifying these tasks in a language supports reproducible research.

The R language operates on vectors and arrays.

Commonly used data types are:

  • integer and numeric vectors;
  • logical vectors;
  • character vectors;
  • factors.

All basic vector types support missing (NA) values.

Arithmetic operations are vectorized to operate element-wise on vectors.

Data vectors are usually combined into table-like objects called data frames.

The Data Analysis Process

A figure that shows the steps usually involved in a data analysis project:

Saving work in a text file or a notebook and tracking changes to your files with a version control system like git will allow you to you or someone else reproduce your results.

Using a system like Rmarkdown to prepare your report avoids the risks of cutting and pasting results and allows you to re-create your report when data changes (as it often will!)

A good resource for setting up your tools to support this is Happy Git and GitHub for the useR.

Some Examples

Working with research data a first step is usually to read and clean the data.

We'll put that off for a little while and work with some data sets made available in R packages.

Data sets available in R packages include:

Old Faithful Eruptions

A simple classic data set is the geyser data frame available in package MASS

data(geyser, package = "MASS")
dim(geyser)
## [1] 299   2
head(geyser)
##   waiting duration
## 1      80 4.016667
## 2      71 2.150000
## 3      57 4.000000
## 4      80 4.000000
## 5      75 4.000000
## 6      77 2.000000
head and tail return the first and last few rows of a data frame. They are useful for quick sanity checks.

The rows represent measurements recorded for eruptions of the Old Faithful geyser in Yellowstone National Park, Wyoming. The variables are:

  • waiting: the time in minutes since the precious eruption;
  • duration: the duration of the eruption.

The durations have a bimodal distribution:

ggplot(geyser) +
    geom_histogram(aes(x = duration), bins = 15, color = "black", fill = "grey")

A basic template for creating a plot with ggplot:

ggplot(data = <DATA>) + <GEOM>(mapping = aes(<MAPPINGS>))

An interesting question is whether the duration can be used to predict when the next eruption will occur.

A plot of the previous duration against the waiting time to the current eruption:

ggplot(geyser) + geom_point(aes(x = lag(duration), y = waiting))
## Warning: Removed 1 rows containing missing values (geom_point).

It looks like a useful rule would be to expect a shorter waiting time after a shorter eruption.

An interesting feature: Many durations are recorded as 2 or 4 minutes. This can also be seen in a histogram with many small bins:

p <- ggplot(geyser) +
    geom_histogram(aes(x = duration, y = ..density..),
                   fill = "grey", color = "black", bins = 50)
p

ggplot produces a plot object. Drawing only happens when the object is printed.

Does this rounding matter?

  • For many analyses it probably doesn't.
  • It might if you wanted to fit normal distributions to the two groups.

Taking 3 minutes as the divide between short and long durations we can compute the means and standard deviations as

d <- geyser$duration
d_short <- d[d < 3]
d_long <- d[d >= 3]
mean(d_short)
## [1] 1.980317
sd(d_short)
## [1] 0.2779829
mean(d_long)
## [1] 4.262113
sd(d_long)
## [1] 0.3937525
mean(d >= 3)
## [1] 0.6488294

An approach that scales better is to compute group summaries using tools from the dplyr tidyverse package.

First, add a type variable:

geyser <- mutate(geyser, type = ifelse(duration < 3, "short", "long"))

The summaries can then be computed as

sgd <- summarize(group_by(geyser, type),
                 mean = mean(duration),
                 sd = sd(duration),
                 n = n())
(sgd <- mutate(sgd, prop = n / sum(n)))
## # A tibble: 2 x 5
##   type   mean    sd     n  prop
##   <chr> <dbl> <dbl> <int> <dbl>
## 1 long   4.26 0.394   194 0.649
## 2 short  1.98 0.278   105 0.351

One way to show the superimposed normal densities:

p <- p +
    stat_function(color = "red",
                  fun = function(x)
                          sgd$prop[1] * dnorm(x, sgd$mean[1], sgd$sd[1])) +
    stat_function(color = "blue",
                  fun = function(x)
                          sgd$prop[2] * dnorm(x, sgd$mean[2], sgd$sd[2]))
p

A ggplot can consist of several layers.

The means and standard deviations are affected by the rounding. Summaries that omit values equal to 2 or 4 minutes can be computed as

geyser2 <- filter(geyser, duration != 2, duration != 4)
sgd2 <- summarize(group_by(geyser2, type),
                  mean = mean(duration),
                  sd = sd(duration),
                  n = n())
(sgd2 <- mutate(sgd2, prop = n / sum(n)))
## # A tibble: 2 x 5
##   type   mean    sd     n  prop
##   <chr> <dbl> <dbl> <int> <dbl>
## 1 long   4.36 0.422   141 0.632
## 2 short  1.97 0.315    82 0.368
summarize, group_by, and mutate are from the dplyr package that implements a grammar of data manipulation.

A plot showing curves computed both ways:

p <- p +
    stat_function(color = "red",
                  linetype = 2,
                  fun = function(x)
                          sgd2$prop[1] * dnorm(x, sgd2$mean[1], sgd2$sd[1])) +
    stat_function(color = "blue",
                  linetype = 2,
                  fun = function(x)
                          sgd2$prop[2] * dnorm(x, sgd2$mean[2], sgd2$sd[2]))
p

Minnesota Barley Yields

A classic data set: Total yield in bushels per acre for 10 varieties at 6 sites in Minnesota in each of two years, 1931 and 1932.

The raw data:

data(barley, package = "lattice")
head(barley)
##      yield   variety year            site
## 1 27.00000 Manchuria 1931 University Farm
## 2 48.86667 Manchuria 1931          Waseca
## 3 27.43334 Manchuria 1931          Morris
## 4 39.93333 Manchuria 1931       Crookston
## 5 32.96667 Manchuria 1931    Grand Rapids
## 6 28.96667 Manchuria 1931          Duluth

Some initial plots:

p1 <- ggplot(barley) + geom_point(aes(x = yield, y = variety))
p2 <- ggplot(barley) + geom_point(aes(x = yield, y = site))
cowplot::plot_grid(p1, p2)

Using color to separate yields in the two years:

p1 <- ggplot(barley) + geom_point(aes(x = yield, y = variety, color = year))
p2 <- ggplot(barley) + geom_point(aes(x = yield, y = site, color = year))
cowplot::plot_grid(p1, p2)

Can we also show site using symbol shape?

ggplot(barley) +
    geom_point(aes(x = yield, y = variety, color = year, shape = site))

There is a lot of interference between shape and color.

Possible improvements:

  • jittering;
  • larger points.
ggplot(barley) +
    geom_point(aes(x = yield, y = variety, color = year, shape = site),
               position = position_jitter(height = 0.15, width = 0),
               size = 2)

Another approach: faceting to produce small multiples.

ggplot(barley) +
    geom_point(aes(x = yield, y = variety, color = year)) +
    facet_wrap(~site)

Focusing on summaries can help. Bar charts are sometimes used for summaries, but dot plots are usually a better choice.

barley_site_year <- summarize(group_by(barley, site, year),
                              yield = mean(yield))
## `summarise()` has grouped output by 'site'. You can override using the `.groups` argument.
p1 <- ggplot(barley_site_year) +
    geom_point(aes(y = site, x = yield, color = year), size = 3)
p2 <- ggplot(barley_site_year) +
    geom_col(aes(x = site, y = yield, fill = year),
             size = 3,
             position = "dodge", width = .4) +
    coord_flip()
cowplot::plot_grid(p1, p2)

Because of the way we perceive bars, it is important to use a zero base line for bar charts.

Hair and Eye Color Data

A data set recording the distribution of hair and eye color and sex in 592 statistics students.

The data set is available as a cross-tabulation; as.data.frame converts it to a data frame.

HairEyeDF <- as.data.frame(HairEyeColor)
head(HairEyeDF)
##    Hair   Eye  Sex Freq
## 1 Black Brown Male   32
## 2 Brown Brown Male   53
## 3   Red Brown Male   10
## 4 Blond Brown Male    3
## 5 Black  Blue Male   11
## 6 Brown  Blue Male   50

Looking at the distribution of eye color:

eye <- summarize(group_by(HairEyeDF, Eye), Freq = sum(Freq))
ggplot(eye) + geom_col(aes(x = Eye, y = Freq), position = "dodge")

Mapping eye color to color in addition to the horizontal axis can help:

ggplot(eye) + geom_col(aes(x = Eye, y = Freq, fill = Eye), position = "dodge")

More sensible colors would be nice but requires a bit of work:

hazel_rgb <- col2rgb("brown") * 0.75 + col2rgb("green") * 0.25
hazel <- do.call(rgb, as.list(hazel_rgb / 255))

cols <- c(Blue = colorspace::lighten(colorspace::desaturate("blue", 0.3), 0.3),
          Green = colorspace::lighten("forestgreen", 0.1),
          Brown = colorspace::lighten("brown", 0.0001), ## 0.3?
          Hazel = colorspace::lighten(hazel, 0.3))

pb <- ggplot(eye) +
    geom_col(aes(x = Eye, y = Freq, fill = Eye), position = "dodge") +
    scale_fill_manual(values = cols)
pb

A stacked bar chart can also be useful:

psb <- ggplot(eye) +
    geom_col(aes(x = "", y = Freq, fill = Eye), color = "lightgrey") +
    scale_fill_manual(values = cols)
psb

A pie chart can be seen as a stacked bar chart in polar coordinates:

(pp <- psb + coord_polar("y"))

The axis and grid are not helpful; a theme adjustment can remove them:

(pp <- pp + theme_void())

Themes are a way to customize the non-data components of plots: i.e. titles, labels, fonts, background, gridlines, and legends. Themes can be used to give plots a consistent customized look.

The ggthemes package provides a number of themes to emulate the style of different publications, for example theme_wsj and theme_economist.

How well do bar charts and pie charts work?

cowplot::plot_grid(pb, pp)

Some questions:

  • Which plot makes it easier to tell whether the proportion of brown-eyed students is larger or smaller that the proportion of blue-eyed students.

  • Which plot makes it easier to tell whether these proportions are larger or smaller than 1/2 or 1/4 or 1/3?

Looking at the proportions within hair color and sex:

eye_hairsex <- mutate(group_by(HairEyeDF, Hair, Sex), Prop = Freq / sum(Freq))
p1 <- ggplot(eye_hairsex) +
    geom_col(aes(x = Eye, y = Prop, fill = Eye)) +
    scale_fill_manual(values = cols) +
    facet_grid(Hair~Sex)
p2 <- ggplot(eye_hairsex) +
    geom_col(aes(x = "", y = Prop, fill = Eye)) +
    scale_fill_manual(values = cols) +
    coord_polar("y")+facet_grid(Hair~Sex) +
    theme_void()
cowplot::plot_grid(p1, p2)

A more complete ggplot template:

ggplot(data = <DATA>) +
    <GEOM>(mapping = aes(<MAPPINGS>),
           stat = <STAT>,
           position = <POSITION>) +
    < ... MORE GEOMS ... > +
    <COORDINATE_ADJUSTMENT> +
    <SCALE_ADJUSTMENT> +
    <FACETING> +
    <THEME_ADJUSTMENT>

Perception and the Grammar of Graphics

river <- scan("data/river.dat")
rd <- data.frame(flow = river, month = seq_along(river))
(pp <- ggplot(rd) + geom_point(aes(x = month, y = flow)))

(pl <- ggplot(rd) + geom_line(aes(x = month, y = flow)))
pp + coord_fixed(3.5)
pl + coord_fixed(3.5)

A Simple Model of Visual Perception

The eyes acquire an image, which is processed through three stages of memory:

  • Iconic memory
  • Working memory, or short-term memory
  • Long-term memory

The first processing stage of an image happens in iconic memory.

  • Images remain in iconic memory for less than a second.
  • Processing in iconic memory is massively parallel and automatic.
  • This is called preattentive processing.

Preattentive processing is a fast recognition process.

Meaningful visual chunks are moved from iconic memory to short term memory.

  • These chunks are used by conscious, or attentive, processing.
  • Attentive processing often involves conscious comparisons or search.
  • Short term memory is limited;
    • information is retained for only a few seconds;
    • only three or fours chunks can be held at a time.

Long term visual memory is built up over a lifetime, though infrequently used visual chunks may become lost.

Visual Design Implications

  • Try to make as much use of preattentive features as possible.

  • Recognize when preattentive features might mislead.

  • For features that require attentive processing keep in mind that working memory is limited.

Some Terms for Describing Visualizations

  • Data to be visualized contains variables or attributes measured on individual items or cases.

  • Links are relationships that may exist among items, e.g. months within a year or countries within a continent.

  • Marks are individual geometric entities used to represent items: points. bars, etc.

  • Aesthetics or visual channels are the visual features of marks that can be used to encode attributes.

The aes(...) expressions establish the mapping between attributes and visual channels.

These ideas closely mirror the structure of the grammar of graphics as implemented in ggplot.

Munzner, T. (2014), Visualization Analysis and Design, CRC Press.

Wilkinson, L. (2005), The Grammar of Graphics, 2nd ed, Springer.

Channels and their Accuracy

A useful distinction among channels:

  • Magnitude channels can reflect order and numeric values, e.g. position on an axis, length, area, brightness.

  • Identity channels can distinguish different values but not reflect order, e.g. hue, shape, grouping.

Some channels are better at conveying information than others.

Munzner's ordering by accuracy:

Magnitude Channels (Ordered, Numerical) Identity Channels (Categorical)
Position on common scale Spatial grouping
Position on unaligned scale Color hue
Length (1D size) Shape
Tilt, angle
Area (2D size)
Depth (3D position)
Color luminance, saturation
Curvature, volume (3D size)

Line width is another channel; not sure there is agreement on its accuracy, but it is not high.

Visual Design Implications

Try to map the most important variables to the strongest channels.

Color

Color is very effective when used well.

But using color well is not easy.

Some of the issues:

  • Perception depends on context.

  • Simple color assignments may not separate equally well.

  • Effectiveness may vary with the medium (screen, projector, print).

  • Some people do not perceive the full specturm of colors.

  • Grey scale printing.

  • Some colors have cultural significance.

  • Cultural significance may vary among cultures and with time.

Color perception is relative:

A note on rainbow colors.

Some tools for selecting palettes include:

  • ColorBrewer; available in the RColorBrewer package.

  • HCL Wizard; also available as hclwizard in the colorspace package.

A Grammar of Data Manipulation

The dplyr package provides a language, or grammar, for data manipulation.

The language contains a number of verbs that operate on tables.

The most commonly used verbs operate on a single data frame:

There are also a number of join verbs that merge several data frames into one.

The tidyr package provides additional verbs, such as gather and spread for reshaping data frames.

The single table verbs can also be used with group_by to work a group at a time instead of applying to the entire data frame.

The design of dplyr is strongly motivated by SQL.

More Examples

These examples start with raw data as you might receive it from a researcher and involve reading and cleaning the data.

Cancer Map

The website http://www.cancer-rates.info/ia provides data on cancer incidence for a number of different cancers in Iowa The data for lung and bronchus cancer in 2011 are available in a csv file in the project.

CSV (comma-separated values) are a common form of data exchange. They are simple text files that are intended to be written and read by a computer.

This CSV file is unusual in that it includes header and footer information that complicate machine reading a bit.

One issue is that a comma isn't a good separator in countries where it is the decimal separator!

We can read the file with read_csv from the readr package.

Looking at the file shows some things that need to be cleaned up:

  • Two header lines at the beginning
  • Some footer lines.
  • Some values codes as ~.

The header can be handled by using skip = 2 in the read_csv call:

fname <- "data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv"
d <- read_csv(fname, skip = 2)
## 
## ── Column specification ────────────────────────────────────────────────────────
## cols(
##   County = col_character(),
##   `Population at Risk` = col_double(),
##   Cases = col_character(),
##   `Crude Rate` = col_character(),
##   `Age-adjusted Rate` = col_character(),
##   `95% Confidence Interval-Lower Limit` = col_character(),
##   `95% Confidence Interval-Upper Limit` = col_character()
## )
## Warning: 3 parsing failures.
## row col  expected    actual                                                                                file
## 101  -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
## 102  -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
## 103  -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
head(d)
## # A tibble: 6 x 7
##   County  `Population at … Cases `Crude Rate` `Age-adjusted R… `95% Confidence …
##   <chr>              <dbl> <chr> <chr>        <chr>            <chr>            
## 1 Union              12570 20    159.11       115.82           69.86            
## 2 Ringgo…             5098 11    215.77       115.03           54.48            
## 3 Monroe              8044 12    149.18       109.99           56.24            
## 4 Page               15926 25    156.98       109.20           70.12            
## 5 Montgo…            10655 17    159.55       99.45            57.41            
## 6 Adams               3996 6     150.15       95.84            35.14            
## # … with 1 more variable: 95% Confidence Interval-Upper Limit <chr>

Let's focus on a few variables and give them more convenient names:

d <- select(d, county = 1, population = 2, count = 3, crude_rate = 4)
tail(d)
## # A tibble: 6 x 4
##   county                                             population count crude_rate
##   <chr>                                                   <dbl> <chr> <chr>     
## 1 Butler                                                  14960 5     33.42     
## 2 Winneshiek                                              21045 ~     ~         
## 3 STATE                                                 3065223 2368  77.25     
## 4 Note: All rates are per 100,000. Rates are age-ad…         NA <NA>  <NA>      
## 5 Rates generated on Jun 12, 2019.                           NA <NA>  <NA>      
## 6 Based on data released Nov 2017.                           NA <NA>  <NA>

One way to remove the footer:

d <- filter(d, ! is.na(population))
tail(d)
## # A tibble: 6 x 4
##   county     population count crude_rate
##   <chr>           <dbl> <chr> <chr>     
## 1 Kossuth         15392 7     45.48     
## 2 Palo Alto        9360 ~     ~         
## 3 Grundy          12474 5     40.08     
## 4 Butler          14960 5     33.42     
## 5 Winneshiek      21045 ~     ~         
## 6 STATE         3065223 2368  77.25

Changing count and crude_rate to numeric changes the ~ entries to missing values (NA) values:

d <- mutate(d, count = as.numeric(count), crude_rate = as.numeric(crude_rate))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

In this case there are no zero case values; two ways to check:

count(d, count == 0)
## # A tibble: 2 x 2
##   `count == 0`     n
##   <lgl>        <int>
## 1 FALSE           96
## 2 NA               4
any(d$count == 0, na.rm = TRUE)
## [1] FALSE

It might be reasonable to assume these values where zero, so replace them with zeros:

d <- replace_na(d, list(count = 0, crude_rate = 0))

A choropleth map uses color or shading to represent values measured for different geographic regions.

In simple cases, like US counties, a choropleth map can be created by finding data for polygons defining county borders and shading the polygons.

Polygon data for US counties in the lower 48 states can be obtained with

m <- map_data("county", "iowa")
head(m)
##        long      lat group order region subregion
## 1 -94.24583 41.50506     1     1   iowa     adair
## 2 -94.24583 41.16129     1     2   iowa     adair
## 3 -94.24583 41.16129     1     3   iowa     adair
## 4 -94.48647 41.16129     1     4   iowa     adair
## 5 -94.70992 41.16129     1     5   iowa     adair
## 6 -94.70992 41.50506     1     6   iowa     adair
m <- select(m, -region)
m <- rename(m, county = subregion)
head(m)
##        long      lat group order county
## 1 -94.24583 41.50506     1     1  adair
## 2 -94.24583 41.16129     1     2  adair
## 3 -94.24583 41.16129     1     3  adair
## 4 -94.48647 41.16129     1     4  adair
## 5 -94.70992 41.16129     1     5  adair
## 6 -94.70992 41.50506     1     6  adair

We will need to merge, or left join, the data we want to plot into the polygon data using the county identifier.

For Iowa this can be done with the county name, but some care is needed.

d <- mutate(d, cname = county, county = tolower(county))

setdiff(d$county, m$county)
## [1] "o'brien" "state"
setdiff(m$county, d$county)
## [1] "obrien"

d <- mutate(d, county = sub("'", "", county))
d <- filter(d, county != "state")

setdiff(d$county, m$county)
## character(0)
setdiff(m$county, d$county)
## character(0)

Define rate1K variable as the number of cases per 1000 inhabitants and left join the data to the polygons:

d <- mutate(d, rate1K = 1000 * (count / population))
md <- left_join(m, d, "county")
head(md)
##        long      lat group order county population count crude_rate cname
## 1 -94.24583 41.50506     1     1  adair       7565    10     132.19 Adair
## 2 -94.24583 41.16129     1     2  adair       7565    10     132.19 Adair
## 3 -94.24583 41.16129     1     3  adair       7565    10     132.19 Adair
## 4 -94.48647 41.16129     1     4  adair       7565    10     132.19 Adair
## 5 -94.70992 41.16129     1     5  adair       7565    10     132.19 Adair
## 6 -94.70992 41.50506     1     6  adair       7565    10     132.19 Adair
##     rate1K
## 1 1.321877
## 2 1.321877
## 3 1.321877
## 4 1.321877
## 5 1.321877
## 6 1.321877

A simple map:

library(ggthemes)
library(viridis)
## Loading required package: viridisLite
ggplot(md) +
    geom_polygon(aes(x = long, y = lat, group = group, fill = rate1K))

An improved version:

library(ggthemes)
library(viridis)
ggplot(md) +
    geom_polygon(aes(x = long, y = lat, group = group,
                     fill = rate1K),
                 color = "grey") +
    scale_fill_viridis(name = "Rate per 1000") +
    theme_map() + coord_quickmap()

A simple interactive version using plotly:

mdl <- mutate(md,
              label = paste(cname, round(rate1K, 1), population, sep = "\n"))
p <- ggplot(mdl) +
    geom_polygon(aes(x = long, y = lat, fill = rate1K, group = group,
                     text = label), 
                 color = "grey") +
    scale_fill_viridis(name = "Rate per 1000") +
    theme_map() + coord_quickmap()
## Warning: Ignoring unknown aesthetics: text

plotly::ggplotly(p, tooltip = "text")

The leaflet package supports more sophisticated interactive maps.

geom_map is alternative to geom_poly that does not require the join operation (it is done internally). It can be a bit tricky to use though.

ggplot(d, aes(map_id = county, fill = count/population)) +
    geom_map(map = rename(m, id = county) , color = "grey") +
    with(m, expand_limits(x = long, y = lat)) +
    scale_fill_viridis() +
    theme_map() + coord_quickmap()

For more complex mapping problems geom_sf is a better choice.

Unemployment Map

Local Area Unemployment Statistics page from the Bureau of Labor Statistics makes available county-level monthly unemployment data for a 14-month window. The file for November 2016 through December 2017 is available is available at http://www.stat.uiowa.edu/~luke/data/laus/laucntycur14-2017.txt and in the project data folder.

This file is a text file but uses a non-standard separator. It is designed for human readability and uses a comma as a thousands separator or grouping mark. It also includes header and footer information. It is still reasonably easy to read in.

One way to read the data into R is:

lausURL <- "data/laucntycur14-2017.txt"
lausUS <- read.table(lausURL,
                     col.names = c("LAUSAreaCode", "State", "County",
                                   "Title", "Period",
                                   "LaborForce", "Employed",
                                   "Unemployed", "UnempRate"),
                     quote = '"', sep = "|", skip = 6,
                     stringsAsFactors = FALSE, strip.white = TRUE,
                     fill = TRUE)
footstart <- grep("------", lausUS$LAUSAreaCode)
lausUS <- lausUS[1:(footstart - 1),]

It may be useful to be able to access the county name and state name separately:

lausUS <- separate(lausUS, Title, c("cname", "scode"),
                   sep = ", ", fill = "right")

The UnempRate variable is read as character data because of missing value encoding, so needs to be converted to numeric:

lausUS <- mutate(lausUS, UnempRate = as.numeric(UnempRate))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

Check for missing values:

sapply(lausUS, function(x) any(is.na(x)))
## LAUSAreaCode        State       County        cname        scode       Period 
##        FALSE        FALSE        FALSE        FALSE         TRUE        FALSE 
##   LaborForce     Employed   Unemployed    UnempRate 
##        FALSE        FALSE        FALSE         TRUE

The state code is missing for the District of Columbia:

unique(filter(select(lausUS, cname, scode), is.na(scode)))
##                  cname scode
## 1 District of Columbia  <NA>

Missing values for UnempRate are all for Puerto Rico and September 2017. Hurricane Maria made landfall on September 20.

unique(filter(select(lausUS, scode, Period, UnempRate), is.na(UnempRate)))
##   scode Period UnempRate
## 1    PR Sep-17        NA

Average unemployment rates over the period can be computed as

avgUS <- summarize(group_by(lausUS, County, State),
                   avg_unemp = mean(UnempRate),
                   cname = unique(cname),
                   scode = unique(scode))
## `summarise()` has grouped output by 'County'. You can override using the `.groups` argument.
head(avgUS)
## # A tibble: 6 x 5
## # Groups:   County [1]
##   County State avg_unemp cname            scode
##    <int> <int>     <dbl> <chr>            <chr>
## 1      1     1      4.21 Autauga County   AL   
## 2      1     4     10.5  Apache County    AZ   
## 3      1     5      3.18 Arkansas County  AR   
## 4      1     6      3.72 Alameda County   CA   
## 5      1     8      2.88 Adams County     CO   
## 6      1     9      4.41 Fairfield County CT

To show average unemployment rates on a map we need to merge the unemployment data with map data.

It is safer to use the numeric FIPS county code, which can be computed as

Add FIPS code to avgUS:

avgUS <- mutate(avgUS, fips = 1000 * State + County)
head(avgUS)
## # A tibble: 6 x 6
## # Groups:   County [1]
##   County State avg_unemp cname            scode  fips
##    <int> <int>     <dbl> <chr>            <chr> <dbl>
## 1      1     1      4.21 Autauga County   AL     1001
## 2      1     4     10.5  Apache County    AZ     4001
## 3      1     5      3.18 Arkansas County  AR     5001
## 4      1     6      3.72 Alameda County   CA     6001
## 5      1     8      2.88 Adams County     CO     8001
## 6      1     9      4.41 Fairfield County CT     9001

The county.fips data frame in the maps package links the FIPS code to region names used by the map data in the maps package.

library(maps)
## 
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
## 
##     map
head(county.fips)
##   fips        polyname
## 1 1001 alabama,autauga
## 2 1003 alabama,baldwin
## 3 1005 alabama,barbour
## 4 1007    alabama,bibb
## 5 1009  alabama,blount
## 6 1011 alabama,bullock

Some issues:

filter(county.fips, grepl("florida,o", polyname))
##    fips              polyname
## 1 12091 florida,okaloosa:main
## 2 12091 florida,okaloosa:spit
## 3 12093    florida,okeechobee
## 4 12095        florida,orange
## 5 12097       florida,osceola
head(select(filter(lausUS, scode == "LA"), cname))
##               cname
## 1     Acadia Parish
## 2      Allen Parish
## 3  Ascension Parish
## 4 Assumption Parish
## 5  Avoyelles Parish
## 6 Beauregard Parish

Cleaning up a bit:

county.fips <- separate(county.fips, polyname,
                        c("state", "county", "part"),
                        sep = "[,:]", fill = "right")
head(county.fips)
##   fips   state  county part
## 1 1001 alabama autauga <NA>
## 2 1003 alabama baldwin <NA>
## 3 1005 alabama barbour <NA>
## 4 1007 alabama    bibb <NA>
## 5 1009 alabama  blount <NA>
## 6 1011 alabama bullock <NA>

County map data for the lower 48 states:

counties_US <- map_data("county")
counties_US <- rename(counties_US, state = region, county = subregion)
counties_US <- left_join(counties_US, county.fips, c("state", "county"))

A choropleth map:

ggplot(left_join(counties_US, avgUS, "fips")) +
    geom_polygon(aes(x = long, y = lat, fill = avg_unemp, group = group)) +
    scale_fill_viridis(name = "Rate", na.value = "red") +
    theme_map() + coord_map() + 
    geom_polygon(aes(x = long, y = lat, group = group),
                 data = map_data("state"), col = "grey", fill = NA)

A version using geom_map leaves out Shannon County, SD (46113) which for some reason is not in the LAUS data.

ggplot(avgUS, aes(fill = avg_unemp, map_id = fips)) +
    geom_map(map = mutate(counties_US, id = fips)) +
    with(counties_US, expand_limits(x = long, y = lat)) +
    scale_fill_viridis(name = "Rate", na.value = "red") +
    theme_map() + coord_map()

Gapminder Childhood Mortality Data

The gapminder package provides a subset of the data from the Gapminder web site. Additional data sets are available.

  • A data set on childhood mortality is available locally as a csv file or an Excel file. The Excel file is also available in the project data folder.

  • The numbers represent number of deaths within the first five years per 1000 births.

Many researchers like to manage their data in a spreadsheet. Being able to read such a sheet directly greatly helps keeping the workflow reproducible.

Many spreadsheets contain header, footers, and other annotations to aid a human viewer.

As long as the data are in a rectangular region it is usually not hard to extract them programmatically.

Loading the data:

library(readxl)
gcm <- read_excel("data/gapminder-under5mortality.xlsx")
names(gcm)[1]
## [1] "Under five mortality"
names(gcm)[1] <- "country"

This data set is in wide format.

A long version is useful for working with ggplot.

tgcm <- gather(gcm, year, u5mort, -1)
head(tgcm)
## # A tibble: 6 x 3
##   country               year   u5mort
##   <chr>                 <chr>   <dbl>
## 1 Abkhazia              1800.0    NA 
## 2 Afghanistan           1800.0   469.
## 3 Akrotiri and Dhekelia 1800.0    NA 
## 4 Albania               1800.0   375.
## 5 Algeria               1800.0   460.
## 6 American Samoa        1800.0    NA
tgcm <- mutate(tgcm, year = as.numeric(year))
head(tgcm)
## # A tibble: 6 x 3
##   country                year u5mort
##   <chr>                 <dbl>  <dbl>
## 1 Abkhazia               1800    NA 
## 2 Afghanistan            1800   469.
## 3 Akrotiri and Dhekelia  1800    NA 
## 4 Albania                1800   375.
## 5 Algeria                1800   460.
## 6 American Samoa         1800    NA

Some explorations:

library(lattice)
## Warning: package 'lattice' was built under R version 4.0.5
p <- ggplot(tgcm) + geom_line(aes(year, u5mort, group = country), alpha = 0.3)
p
## Warning: Removed 18644 row(s) containing missing values (geom_path).

plotly::ggplotly(p)

Some selected countries:

countries <- c("United States", "United Kingdom", "Germany", "China", "Egypt")
tcgm1 <- filter(tgcm, country %in% countries)
ggplot(tcgm1) + geom_line(aes(x = year, y = u5mort, color = country))

Examining the missing values:

tgcm_miss <- summarize(group_by(tgcm, country), anyNA = any(is.na(u5mort)))
tgcm_miss <- filter(tgcm_miss, anyNA)$country
p <- ggplot(filter(tgcm, country %in% tgcm_miss)) +
    geom_line(aes(x = year, y = u5mort, group = country), na.rm = TRUE)
p

plotly::ggplotly(p)
LS0tCnRpdGxlOiAiQmFzaWMgRGF0YSBXcmFuZ2xpbmcgYW5kIERhdGEgVmlzdWFsaXphdGlvbiBpbiBSIgphdXRob3I6ICJMdWtlIFRpZXJuZXkiCmRhdGU6ICIxNyBKdW5lLCAyMDE5IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3IgZ2xvYmFsX29wdGlvbnMsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZT1UUlVFKQpgYGAKCiMjIEludHJvZHVjdGlvbgoKSW4gdGhpcyBjbGFzcyBJIHdpbGwKCi0gQnJpZWZseSBvdXRsaW5lIHRoZSBoaXN0b3J5IG9mIFIuCi0gVXNpbmcgc29tZSBleGFtcGxlcyBicmllZmx5IHNob3cgaG93IHRvIGRvIGRhdGEgd3JhbmdsaW5nCiAgYW5kIHZpc3VhbGl6ZSBkYXRhIGluIFIuCiAKTWF0ZXJpYWxzIGZvciB0aGlzIGNsYXNzIGFyZSBhdmFpbGFibGUgb24gR2l0SHViIGF0CjxodHRwczovL2dpdGh1Yi5jb20vbHRpZXJuZXkvU0lCUy1XVi0yMDE5LmdpdD4uCgotIFlvdSBjYW4gYWNjZXNzIGl0IGFzIGFuIFJTdHVkaW8gcHJvamVjdCBieSBmb2xsb3dpbmcgdGhlIG1lbnUgc2VsZWN0aW9uCiAgKipGaWxlID4gTmV3IFByb2plY3QgPiBWZXJzaW9uIENvbnRyb2wgPiBHaXQqKiBhbmQgc3BlY2lmeWluZyB0aGlzIHVybC4KLSBZb3UgY2EgdXNlIHRoZSBgZ2l0YCBjb21tYW5kIGxpbmUgY2xpZW50IHdpdGgKICAgIGBgYHNoZWxsCmdpdCBjbG9uZSBodHRwczovL2dpdGh1Yi5jb20vbHRpZXJuZXkvU0lCUy1XVi0yMDE5LmdpdAogICAgYGBgCgpNYXRlcmlhbHMgZm9yIG91ciBfRGF0YSBWaXN1YWxpemF0aW9uIGFuZCBEYXRhClRlY2hub2xvZ2llc18gY291cnNlIGFyZSBhdmFpbGFibGUgYXQKIDxodHRwOi8vd3d3LnN0YXQudWlvd2EuZWR1L35sdWtlL2NsYXNzZXMvU1RBVDQ1ODAvPgoKU29tZSB0b29scyBJIHdpbGwgYmUgdXNpbmc6CgotIFRoZSBbUlN0dWRpb10oaHR0cHM6Ly93d3cucnN0dWRpb24uY29tKSBJREUuCi0gTWFueSBmZWF0dXJlcyBmcm9tIHRoZSBiYXNpYyBbUl0oaHR0cHM6Ly93d3cuci1wcm9qZWN0Lm9yZykgZGlzdHJpYnV0aW9uLgotIFNvbWUgdG9vbHMgZnJvbSB0aGUgW190aWR5dmVyc2VfXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykuCi0gVGhlIFtgZ2dwbG90YF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSBwYWNrYWdlIGJhc2VkIG9uCiAgdGhlIF9HcmFtbWFyIG9mIEdyYXBoaWNzXyBmcmFtZXdvcmsuCgpNb3N0IG9mIHRoZSBwYWNrYWdlcyBhcmUgbG9hZGVkIGJ5IGxvYWRpbmcgdGhlIGB0aWR5dmVyc2VgIHBhY2thZ2UKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKClVzZWZ1bCByZWZlcmVuY2VzOgoKPiBIYWRsZXkgV2lja2hhbSBhbmQgR2FycmV0dCBHcm9sZW11bmQgKDIwMTYpLCBbX1IgZm9yIERhdGEKPiBTY2llbmNlX10oaHR0cDovL3I0ZHMuaGFkLmNvLm56LyksIE8nUmVpbGx5LgoKPiBDbGF1cyBPLiBXaWxrZSAoMjAxOSksIFtfRnVuZGFtZW50YWxzIG9mIERhdGEKPiAgVmlzdWFsaXphdGlvbl9dKGh0dHBzOi8vc2VyaWFsbWVudG9yLmNvbS9kYXRhdml6LyksIE8nUmVpbGx5LgogIApBc2sgcXVlc3Rpb25zIGFueSB0aW1lIQoKIAojIyMgVGhlIFIgTGFuZ3VhZ2UKClIgaXMgYSBsYW5ndWFnZSBmb3IgZGF0YSBhbmFseXNpcyBhbmQgZ3JhcGhpY3MuCgotIFIgd2FzIG9yaWdpbmFsbHkgZGV2ZWxvcGVkIGJ5IFJvYmVydCBHZW50bGVtYW4gYW5kIFJvc3MgSWhha2EgaW4gdGhlCiAgZWFybHkgMTk5MCdzIGZvciBhIE1hY2ludG9zaCBjb21wdXRlciBsYWIgYXQgVS4gb2YgQXVja2xhbmQsIE5ldyBaZWFsYW5kLgotIFIgaXMgYmFzZWQgb24gdGhlIFMgbGFuZ3VhZ2UgZGV2ZWxvcGVkIGJ5IEpvaG4gQ2hhbWJlcnMgYW5kCiAgb3RoZXJzIGF0IEJlbGwgTGFicy4KClIgaXMgYW4gT3BlbiBTb3VyY2UgcHJvamVjdC4KCi0gU2luY2UgMTk5NyBSIGlzIGRldmVsb3BlZCBhbmQgbWFpbnRhaW5lZCBieSB0aGUgUi1jb3JlIGdyb3VwLAogIHdpdGggYXJvdW5kIDIwIG1lbWJlcnMgbG9jYXRlZCBpbiBtYW9yIHRoYW4gMTAgZGlmZmVyZW50IGNvdW50cmllcy4KClIgaXMgd2lkZWx5IHVzZWQgaW4gdGhlIGZpZWxkIG9mIHN0YXRpc3RpY3MgYW5kIGJleW9uZCwgZXNwZWNpYWxseSBpbgogIHVuaXZlcnNpdHkgZW52aXJvbm1lbnRzLgoKLSBSIGhhcyBiZWNvbWUgdGhlIHByaW1hcnkgZnJhbWV3b3JrIGZvciBkZXZlbG9waW5nIGFuZCBtYWtpbmcgYXZhaWxhYmxlCiAgbmV3IHN0YXRpc3RpY2FsIG1ldGhvZG9sb2d5LgotIE1hbnkgKG5vdyBvdmVyIDE0LDAwMCkgZXh0ZW5zaW9uIHBhY2thZ2VzIGFyZSBhdmFpbGFibGUgdGhyb3VnaCBDUkFOIG9yCiAgc2ltaWxhciByZXBvc2l0b3JpZXMuCgojIyMgV29ya2luZyB3aXRoIFIKClIgaXMgZGVzaWduZWQgZm9yIGludGVyYWN0aXZlIGRhdGEgZXhwbG9yYXRpb24uCgotIEludGVyYWN0aW9uIGlzIHRocm91Z2ggYSBfcmVhZC1ldmFsLXByaW50IGxvb3AgKFJFUEwpXy4KLSBUaGlzIGlzIGFsc28gY2FsbGVkIGEgX2NvbW1hbmQgbGluZSBpbnRlcmZhY2UgKENMSSlfLgoKQWxsIGNvbXB1dGF0aW9ucyBhcmUgc3BlY2lmaWVkIGluIHRoZSBSIGxhbmd1YWdlLgoKLSBFdmVuIGZvciBzaW1wbGUgdGFza3MgeW91IG5lZWQgdG8ga25vdyBhIGxpdHRsZSBvZiB0aGUgbGFuZ3VhZ2UuCi0gQWZ0ZXIgbGVhcm5pbmcgdG8gZG8gc2ltcGxlIHRhc2tzIHlvdSBrbm93IHNvbWUgb2YgdGhlIGxhbmd1YWdlLgoKVGhlIGxhbmd1YWdlIGlzIHVzZWQgdG8KICAgIAotIHByZXBhcmUgZGF0YSBmb3IgYW5hbHlzaXM7Ci0gc3BlY2lmeSBpbmRpdmlkdWFsIGFuYWx5c2VzOwotIHByb2dyYW0gcmVwZWF0ZWQgb3Igc2ltaWxhciBhbmFseXNlczsKLSBwcm9ncmFtIG5ldyBtZXRob2RzIG9mIGFuYWx5c2lzLgoKU3BlY2lmeWluZyB0aGVzZSB0YXNrcyBpbiBhIGxhbmd1YWdlIHN1cHBvcnRzIF9yZXByb2R1Y2libGUgcmVzZWFyY2hfLgoKVGhlIFIgbGFuZ3VhZ2Ugb3BlcmF0ZXMgb24gdmVjdG9ycyBhbmQgYXJyYXlzLgoKQ29tbW9ubHkgdXNlZCBkYXRhIHR5cGVzIGFyZToKCi0gaW50ZWdlciBhbmQgbnVtZXJpYyB2ZWN0b3JzOwotIGxvZ2ljYWwgdmVjdG9yczsKLSBjaGFyYWN0ZXIgdmVjdG9yczsKLSBmYWN0b3JzLgoKQWxsIGJhc2ljIHZlY3RvciB0eXBlcyBzdXBwb3J0IG1pc3NpbmcgKGBOQWApIHZhbHVlcy4KCkFyaXRobWV0aWMgb3BlcmF0aW9ucyBhcmUgdmVjdG9yaXplZCB0byBvcGVyYXRlIGVsZW1lbnQtd2lzZSBvbiB2ZWN0b3JzLgoKRGF0YSB2ZWN0b3JzIGFyZSB1c3VhbGx5IGNvbWJpbmVkIGludG8gdGFibGUtbGlrZSBvYmplY3RzIGNhbGxlZCBfZGF0YQpmcmFtZXNfLgoKCiMjIyBUaGUgRGF0YSBBbmFseXNpcyBQcm9jZXNzCgpBIGZpZ3VyZSB0aGF0IHNob3dzIHRoZSBzdGVwcyB1c3VhbGx5IGludm9sdmVkIGluIGEgZGF0YSBhbmFseXNpcwpwcm9qZWN0OgoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeShub21ub21sKQpgYGAKPGNlbnRlcj4KYGBge25vbW5vbWwsIGVjaG8gPSBGQUxTRX0KI3BhZGRpbmc6IDI1CiNmb250c2l6ZTogMTgKI2ZpbGw6ICNFMURBRkY7ICNENEE5RkYKI3N0cm9rZTogIzg1MTVDNwojbGluZXdpZHRoOiAyCgpbSW1wb3J0XSAtPiBbVW5kZXJzdGFuZF0KW1VuZGVyc3RhbmQgfAogIFtXcmFuZ2xlXSAtPiBbVmlzdWFsaXplXQogIFtWaXN1YWxpemVdIC0+IFtNb2RlbF0KICBbTW9kZWxdIC0+IFtXcmFuZ2xlXQpdCltVbmRlcnN0YW5kXSAtPiBbQ29tbXVuaWNhdGVdCmBgYAo8L2NlbnRlcj4KClNhdmluZyB3b3JrIGluIGEgdGV4dCBmaWxlIG9yIGEgbm90ZWJvb2sgYW5kIHRyYWNraW5nIGNoYW5nZXMgdG8KeW91ciBmaWxlcyB3aXRoIGEgdmVyc2lvbiBjb250cm9sIHN5c3RlbSBsaWtlCltgZ2l0YF0oaHR0cHM6Ly9naXQtc2NtLmNvbS8pIHdpbGwgYWxsb3cgeW91IHRvIHlvdSBvciBzb21lb25lIGVsc2UKcmVwcm9kdWNlIHlvdXIgcmVzdWx0cy4KClVzaW5nIGEgc3lzdGVtIGxpa2UgUm1hcmtkb3duIHRvIHByZXBhcmUgeW91ciByZXBvcnQgYXZvaWRzIHRoZSByaXNrcwpvZiBjdXR0aW5nIGFuZCBwYXN0aW5nIHJlc3VsdHMgYW5kIGFsbG93cyB5b3UgdG8gcmUtY3JlYXRlIHlvdXIgcmVwb3J0CndoZW4gZGF0YSBjaGFuZ2VzIChhcyBpdCBvZnRlbiB3aWxsISkKCkEgZ29vZCByZXNvdXJjZSBmb3Igc2V0dGluZyB1cCB5b3VyIHRvb2xzIHRvIHN1cHBvcnQgdGhpcyBpcyBbX0hhcHB5CkdpdCBhbmQgR2l0SHViIGZvciB0aGUgdXNlUl9dKGh0dHBzOi8vaGFwcHlnaXR3aXRoci5jb20vKS4KCgojIyBTb21lIEV4YW1wbGVzCgpXb3JraW5nIHdpdGggcmVzZWFyY2ggZGF0YSBhIGZpcnN0IHN0ZXAgaXMgdXN1YWxseSB0byByZWFkIGFuZCBjbGVhbgp0aGUgZGF0YS4KCldlJ2xsIHB1dCB0aGF0IG9mZiBmb3IgYSBsaXR0bGUgd2hpbGUgYW5kIHdvcmsgd2l0aCBzb21lIGRhdGEgc2V0cwptYWRlIGF2YWlsYWJsZSBpbiBSIHBhY2thZ2VzLgoKRGF0YSBzZXRzIGF2YWlsYWJsZSBpbiBSIHBhY2thZ2VzIGluY2x1ZGU6CgotIG1hbnkgY2xhc3NpYyBkYXRhIHNldHM7Ci0gbmV3ZXIsIG9mdGVuIGxhcmdlciwgZGF0YSBzZXRzIHVzZWZ1bCBmb3IgbGVhcm5pbmc7Ci0gY3VycmVudCBkYXRhIG9idGFpbmVkIGJ5IHF1ZXJ5aW5nIHdlYiBBUElzLgoKCiMjIyBPbGQgRmFpdGhmdWwgRXJ1cHRpb25zCgpBIHNpbXBsZSBjbGFzc2ljIGRhdGEgc2V0IGlzIHRoZSBgZ2V5c2VyYCBkYXRhIGZyYW1lIGF2YWlsYWJsZSBpbgpwYWNrYWdlIGBNQVNTYAoKYGBge3J9CmRhdGEoZ2V5c2VyLCBwYWNrYWdlID0gIk1BU1MiKQpkaW0oZ2V5c2VyKQpoZWFkKGdleXNlcikKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gYGhlYWRgIGFuZCBgdGFpbGAgcmV0dXJuIHRoZSBmaXJzdCBhbmQKbGFzdCBmZXcgcm93cyBvZiBhIGRhdGEgZnJhbWUuIFRoZXkgYXJlIHVzZWZ1bCBmb3IgcXVpY2sgc2FuaXR5CmNoZWNrcy4gIDwvZGl2PgoKVGhlIHJvd3MgcmVwcmVzZW50IG1lYXN1cmVtZW50cyByZWNvcmRlZCBmb3IgZXJ1cHRpb25zIG9mIHRoZSBfT2xkCkZhaXRoZnVsXyBnZXlzZXIgaW4gWWVsbG93c3RvbmUgTmF0aW9uYWwgUGFyaywgV3lvbWluZy4gVGhlIHZhcmlhYmxlcwphcmU6CgotIGB3YWl0aW5nYDogdGhlIHRpbWUgaW4gbWludXRlcyBzaW5jZSB0aGUgcHJlY2lvdXMgZXJ1cHRpb247Ci0gYGR1cmF0aW9uYDogdGhlIGR1cmF0aW9uIG9mIHRoZSBlcnVwdGlvbi4KClRoZSBkdXJhdGlvbnMgaGF2ZSBhIGJpbW9kYWwgZGlzdHJpYnV0aW9uOgoKYGBge3J9CmdncGxvdChnZXlzZXIpICsKICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gZHVyYXRpb24pLCBiaW5zID0gMTUsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJncmV5IikKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gQSBiYXNpYyB0ZW1wbGF0ZSBmb3IgY3JlYXRpbmcgYSBwbG90CndpdGggYGdncGxvdGA6CgpgYGByCmdncGxvdChkYXRhID0gPERBVEE+KSArIDxHRU9NPihtYXBwaW5nID0gYWVzKDxNQVBQSU5HUz4pKQpgYGAKPC9kaXY+CgpBbiBpbnRlcmVzdGluZyBxdWVzdGlvbiBpcyB3aGV0aGVyIHRoZSBkdXJhdGlvbiBjYW4gYmUgdXNlZCB0byBwcmVkaWN0CndoZW4gdGhlIF9uZXh0XyBlcnVwdGlvbiB3aWxsIG9jY3VyLgoKQSBwbG90IG9mIHRoZSBfcHJldmlvdXNfIGR1cmF0aW9uIGFnYWluc3QgdGhlIHdhaXRpbmcgdGltZSB0byB0aGUKY3VycmVudCBlcnVwdGlvbjoKCmBgYHtyfQpnZ3Bsb3QoZ2V5c2VyKSArIGdlb21fcG9pbnQoYWVzKHggPSBsYWcoZHVyYXRpb24pLCB5ID0gd2FpdGluZykpCmBgYAoKSXQgbG9va3MgbGlrZSBhIHVzZWZ1bCBydWxlIHdvdWxkIGJlIHRvIGV4cGVjdCBhIHNob3J0ZXIgd2FpdGluZyB0aW1lCmFmdGVyIGEgc2hvcnRlciBlcnVwdGlvbi4KCkFuIGludGVyZXN0aW5nIGZlYXR1cmU6IE1hbnkgZHVyYXRpb25zIGFyZSByZWNvcmRlZCBhcyAyIG9yIDQgbWludXRlcy4KVGhpcyBjYW4gYWxzbyBiZSBzZWVuIGluIGEgaGlzdG9ncmFtIHdpdGggbWFueSBzbWFsbCBiaW5zOgoKYGBge3J9CnAgPC0gZ2dwbG90KGdleXNlcikgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBkdXJhdGlvbiwgeSA9IC4uZGVuc2l0eS4uKSwKICAgICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleSIsIGNvbG9yID0gImJsYWNrIiwgYmlucyA9IDUwKQpwCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IGBnZ3Bsb3RgIHByb2R1Y2VzIGEgcGxvdApvYmplY3QuIERyYXdpbmcgb25seSBoYXBwZW5zIHdoZW4gdGhlIG9iamVjdCBpcyBwcmludGVkLiAgPC9kaXY+CgpEb2VzIHRoaXMgcm91bmRpbmcgbWF0dGVyPwoKLSBGb3IgbWFueSBhbmFseXNlcyBpdCBwcm9iYWJseSBkb2Vzbid0LgotIEl0IG1pZ2h0IGlmIHlvdSB3YW50ZWQgdG8gZml0IG5vcm1hbCBkaXN0cmlidXRpb25zIHRvIHRoZSB0d28gZ3JvdXBzLgoKClRha2luZyAzIG1pbnV0ZXMgYXMgdGhlIGRpdmlkZSBiZXR3ZWVuIHNob3J0IGFuZCBsb25nIGR1cmF0aW9ucyB3ZSBjYW4KY29tcHV0ZSB0aGUgbWVhbnMgYW5kIHN0YW5kYXJkIGRldmlhdGlvbnMgYXMKCmBgYHtyfQpkIDwtIGdleXNlciRkdXJhdGlvbgpkX3Nob3J0IDwtIGRbZCA8IDNdCmRfbG9uZyA8LSBkW2QgPj0gM10KbWVhbihkX3Nob3J0KQpzZChkX3Nob3J0KQptZWFuKGRfbG9uZykKc2QoZF9sb25nKQptZWFuKGQgPj0gMykKYGBgCgpBbiBhcHByb2FjaCB0aGF0IHNjYWxlcyBiZXR0ZXIgaXMgdG8gY29tcHV0ZSBncm91cCBzdW1tYXJpZXMgdXNpbmcKdG9vbHMgZnJvbSB0aGUgYGRwbHlyYCBgdGlkeXZlcnNlYCBwYWNrYWdlLgoKRmlyc3QsIGFkZCBhIGB0eXBlYCB2YXJpYWJsZToKCmBgYHtyfQpnZXlzZXIgPC0gbXV0YXRlKGdleXNlciwgdHlwZSA9IGlmZWxzZShkdXJhdGlvbiA8IDMsICJzaG9ydCIsICJsb25nIikpCmBgYAoKVGhlIHN1bW1hcmllcyBjYW4gdGhlbiBiZSBjb21wdXRlZCBhcwoKYGBge3J9CnNnZCA8LSBzdW1tYXJpemUoZ3JvdXBfYnkoZ2V5c2VyLCB0eXBlKSwKICAgICAgICAgICAgICAgICBtZWFuID0gbWVhbihkdXJhdGlvbiksCiAgICAgICAgICAgICAgICAgc2QgPSBzZChkdXJhdGlvbiksCiAgICAgICAgICAgICAgICAgbiA9IG4oKSkKKHNnZCA8LSBtdXRhdGUoc2dkLCBwcm9wID0gbiAvIHN1bShuKSkpCmBgYAoKT25lIHdheSB0byBzaG93IHRoZSBzdXBlcmltcG9zZWQgbm9ybWFsIGRlbnNpdGllczoKCmBgYHtyfQpwIDwtIHAgKwogICAgc3RhdF9mdW5jdGlvbihjb2xvciA9ICJyZWQiLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZCRwcm9wWzFdICogZG5vcm0oeCwgc2dkJG1lYW5bMV0sIHNnZCRzZFsxXSkpICsKICAgIHN0YXRfZnVuY3Rpb24oY29sb3IgPSAiYmx1ZSIsCiAgICAgICAgICAgICAgICAgIGZ1biA9IGZ1bmN0aW9uKHgpCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2dkJHByb3BbMl0gKiBkbm9ybSh4LCBzZ2QkbWVhblsyXSwgc2dkJHNkWzJdKSkKcApgYGAKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBBIGBnZ3Bsb3RgIGNhbiBjb25zaXN0IG9mIHNldmVyYWwKX2xheWVyc18uICA8L2Rpdj4KClRoZSBtZWFucyBhbmQgc3RhbmRhcmQgZGV2aWF0aW9ucyBhcmUgYWZmZWN0ZWQgYnkgdGhlCnJvdW5kaW5nLiBTdW1tYXJpZXMgdGhhdCBvbWl0IHZhbHVlcyBlcXVhbCB0byAyIG9yIDQgbWludXRlcyBjYW4gYmUKY29tcHV0ZWQgYXMKCmBgYHtyfQpnZXlzZXIyIDwtIGZpbHRlcihnZXlzZXIsIGR1cmF0aW9uICE9IDIsIGR1cmF0aW9uICE9IDQpCnNnZDIgPC0gc3VtbWFyaXplKGdyb3VwX2J5KGdleXNlcjIsIHR5cGUpLAogICAgICAgICAgICAgICAgICBtZWFuID0gbWVhbihkdXJhdGlvbiksCiAgICAgICAgICAgICAgICAgIHNkID0gc2QoZHVyYXRpb24pLAogICAgICAgICAgICAgICAgICBuID0gbigpKQooc2dkMiA8LSBtdXRhdGUoc2dkMiwgcHJvcCA9IG4gLyBzdW0obikpKQpgYGAKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBgc3VtbWFyaXplYCwgYGdyb3VwX2J5YCwgYW5kIGBtdXRhdGVgCmFyZSBmcm9tIHRoZSBgZHBseXJgIHBhY2thZ2UgdGhhdCBpbXBsZW1lbnRzIGEgX2dyYW1tYXIgb2YgZGF0YQptYW5pcHVsYXRpb25fLiAgPC9kaXY+CgpBIHBsb3Qgc2hvd2luZyBjdXJ2ZXMgY29tcHV0ZWQgYm90aCB3YXlzOgoKYGBge3J9CnAgPC0gcCArCiAgICBzdGF0X2Z1bmN0aW9uKGNvbG9yID0gInJlZCIsCiAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QyJHByb3BbMV0gKiBkbm9ybSh4LCBzZ2QyJG1lYW5bMV0sIHNnZDIkc2RbMV0pKSArCiAgICBzdGF0X2Z1bmN0aW9uKGNvbG9yID0gImJsdWUiLAogICAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsCiAgICAgICAgICAgICAgICAgIGZ1biA9IGZ1bmN0aW9uKHgpCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2dkMiRwcm9wWzJdICogZG5vcm0oeCwgc2dkMiRtZWFuWzJdLCBzZ2QyJHNkWzJdKSkKcApgYGAKCmBgYHtyLCBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KIyMgRmFuY2llciB2ZXJzaW9uIHRoYXQgZ2V0cyBhIGNvbG9yIGxlZ2VuZC4KIyMgQ291bGQgYWxzbyBnZXQgYSBsaW5lIHR5cGUgbGVnZW5kLgpwIDwtIGdncGxvdChnZXlzZXIpICsKICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gZHVyYXRpb24sIHkgPSAuLmRlbnNpdHkuLiksCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLCBjb2xvciA9ICJibGFjayIsIGJpbnMgPSA1MCkKcCA8LSBwICsgCiAgICBzdGF0X2Z1bmN0aW9uKGFlcyhjb2xvciA9IHR5cGUpLAogICAgICAgICAgICAgICAgICBkYXRhID0gZmlsdGVyKHNnZCwgdHlwZSA9PSAibG9uZyIpLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZCRwcm9wWzFdICogZG5vcm0oeCwgc2dkJG1lYW5bMV0sIHNnZCRzZFsxXSkpICsKICAgIHN0YXRfZnVuY3Rpb24oYWVzKGNvbG9yID0gdHlwZSksCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBmaWx0ZXIoc2dkLCB0eXBlID09ICJzaG9ydCIpLAogICAgICAgICAgICAgICAgICBmdW4gPSBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgICAgIHNnZCRwcm9wWzJdICogZG5vcm0oeCwgc2dkJG1lYW5bMl0sIHNnZCRzZFsyXSkpCnAKCnAgPC0gcCArCiAgICAgc3RhdF9mdW5jdGlvbihhZXMoY29sb3IgPSB0eXBlKSwKICAgICAgICAgICAgICAgICAgZGF0YSA9IGZpbHRlcihzZ2QyLCB0eXBlID09ICJsb25nIiksCiAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QyJHByb3BbMV0gKiBkbm9ybSh4LCBzZ2QyJG1lYW5bMV0sIHNnZDIkc2RbMV0pKSArCiAgICBzdGF0X2Z1bmN0aW9uKGFlcyhjb2xvciA9IHR5cGUpLAogICAgICAgICAgICAgICAgICBkYXRhID0gZmlsdGVyKHNnZDIsIHR5cGUgPT0gInNob3J0IiksCiAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QyJHByb3BbMl0gKiBkbm9ybSh4LCBzZ2QyJG1lYW5bMl0sIHNnZDIkc2RbMl0pKQpwCmBgYAoKCiMjIyBNaW5uZXNvdGEgQmFybGV5IFlpZWxkcwoKQSBjbGFzc2ljIGRhdGEgc2V0OiBUb3RhbCB5aWVsZCBpbiBidXNoZWxzIHBlciBhY3JlIGZvciAxMCB2YXJpZXRpZXMKYXQgNiBzaXRlcyBpbiBNaW5uZXNvdGEgaW4gZWFjaCBvZiB0d28geWVhcnMsIDE5MzEgYW5kIDE5MzIuCgpUaGUgcmF3IGRhdGE6CgpgYGB7cn0KZGF0YShiYXJsZXksIHBhY2thZ2UgPSAibGF0dGljZSIpCmhlYWQoYmFybGV5KQpgYGAKClNvbWUgaW5pdGlhbCBwbG90czoKCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KcDEgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5KSkKcDIgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSBzaXRlKSkKY293cGxvdDo6cGxvdF9ncmlkKHAxLCBwMikKYGBgCgpVc2luZyBjb2xvciB0byBzZXBhcmF0ZSB5aWVsZHMgaW4gdGhlIHR3byB5ZWFyczoKCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KcDEgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIpKQpwMiA8LSBnZ3Bsb3QoYmFybGV5KSArIGdlb21fcG9pbnQoYWVzKHggPSB5aWVsZCwgeSA9IHNpdGUsIGNvbG9yID0geWVhcikpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwMSwgcDIpCgpgYGAKCkNhbiB3ZSBhbHNvIHNob3cgYHNpdGVgIHVzaW5nIHN5bWJvbCBzaGFwZT8KCmBgYHtyfQpnZ3Bsb3QoYmFybGV5KSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIsIHNoYXBlID0gc2l0ZSkpCmBgYAoKVGhlcmUgaXMgYSBsb3Qgb2YgX2ludGVyZmVyZW5jZV8gYmV0d2VlbiBzaGFwZSBhbmQgY29sb3IuCgpQb3NzaWJsZSBpbXByb3ZlbWVudHM6CgotIGppdHRlcmluZzsKLSBsYXJnZXIgcG9pbnRzLgoKYGBge3J9CmdncGxvdChiYXJsZXkpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSB5aWVsZCwgeSA9IHZhcmlldHksIGNvbG9yID0geWVhciwgc2hhcGUgPSBzaXRlKSwKICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIoaGVpZ2h0ID0gMC4xNSwgd2lkdGggPSAwKSwKICAgICAgICAgICAgICAgc2l6ZSA9IDIpCmBgYAoKQW5vdGhlciBhcHByb2FjaDogX2ZhY2V0aW5nXyB0byBwcm9kdWNlIF9zbWFsbCBtdWx0aXBsZXNfLgoKYGBge3IsIGZpZy53aWR0aCA9IDEwfQpnZ3Bsb3QoYmFybGV5KSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIpKSArCiAgICBmYWNldF93cmFwKH5zaXRlKQpgYGAKCkZvY3VzaW5nIG9uIHN1bW1hcmllcyBjYW4gaGVscC4gX0JhciBjaGFydHNfIGFyZSBzb21ldGltZXMgdXNlZCBmb3IKc3VtbWFyaWVzLCBidXQgX2RvdCBwbG90c18gYXJlIHVzdWFsbHkgYSBiZXR0ZXIgY2hvaWNlLgoKYGBge3IsIGZpZy53aWR0aCA9IDEwfQpiYXJsZXlfc2l0ZV95ZWFyIDwtIHN1bW1hcml6ZShncm91cF9ieShiYXJsZXksIHNpdGUsIHllYXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5aWVsZCA9IG1lYW4oeWllbGQpKQpwMSA8LSBnZ3Bsb3QoYmFybGV5X3NpdGVfeWVhcikgKwogICAgZ2VvbV9wb2ludChhZXMoeSA9IHNpdGUsIHggPSB5aWVsZCwgY29sb3IgPSB5ZWFyKSwgc2l6ZSA9IDMpCnAyIDwtIGdncGxvdChiYXJsZXlfc2l0ZV95ZWFyKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IHNpdGUsIHkgPSB5aWVsZCwgZmlsbCA9IHllYXIpLAogICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gLjQpICsKICAgIGNvb3JkX2ZsaXAoKQpjb3dwbG90OjpwbG90X2dyaWQocDEsIHAyKQpgYGAKCkJlY2F1c2Ugb2YgdGhlIHdheSB3ZSBwZXJjZWl2ZSBiYXJzLCBpdCBpcyBpbXBvcnRhbnQgdG8gdXNlIGEgW3plcm8KYmFzZSBsaW5lIGZvciBiYXIKY2hhcnRzXShodHRwczovL2Zsb3dpbmdkYXRhLmNvbS8yMDE1LzA4LzMxL2Jhci1jaGFydC1iYXNlbGluZXMtc3RhcnQtYXQtemVyby8pLgoKIVtdKGltZy92aXozLTUyMHgyOTQuanBnKQoKIVtdKGltZy92aXo1LTUyMHgyODAuanBnKQoKCiMjIyBIYWlyIGFuZCBFeWUgQ29sb3IgRGF0YQoKQSBkYXRhIHNldCByZWNvcmRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBoYWlyIGFuZCBleWUgY29sb3IgYW5kIHNleCBpbgo1OTIgc3RhdGlzdGljcyBzdHVkZW50cy4KClRoZSBkYXRhIHNldCBpcyBhdmFpbGFibGUgYXMgYSBfY3Jvc3MtdGFidWxhdGlvbl87IGBhcy5kYXRhLmZyYW1lYApjb252ZXJ0cyBpdCB0byBhIGRhdGEgZnJhbWUuCgpgYGB7cn0KSGFpckV5ZURGIDwtIGFzLmRhdGEuZnJhbWUoSGFpckV5ZUNvbG9yKQpoZWFkKEhhaXJFeWVERikKYGBgCgpMb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgZXllIGNvbG9yOgoJCmBgYHtyfQpleWUgPC0gc3VtbWFyaXplKGdyb3VwX2J5KEhhaXJFeWVERiwgRXllKSwgRnJlcSA9IHN1bShGcmVxKSkKZ2dwbG90KGV5ZSkgKyBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEpLCBwb3NpdGlvbiA9ICJkb2RnZSIpCmBgYAoKTWFwcGluZyBleWUgY29sb3IgdG8gY29sb3IgaW4gYWRkaXRpb24gdG8gdGhlIGhvcml6b250YWwgYXhpcyBjYW4gaGVscDoKCmBgYHtyfQpnZ3Bsb3QoZXllKSArIGdlb21fY29sKGFlcyh4ID0gRXllLCB5ID0gRnJlcSwgZmlsbCA9IEV5ZSksIHBvc2l0aW9uID0gImRvZGdlIikKYGBgCgpNb3JlIHNlbnNpYmxlIGNvbG9ycyB3b3VsZCBiZSBuaWNlIGJ1dCByZXF1aXJlcyBhIGJpdCBvZiB3b3JrOgoKYGBge3J9CmhhemVsX3JnYiA8LSBjb2wycmdiKCJicm93biIpICogMC43NSArIGNvbDJyZ2IoImdyZWVuIikgKiAwLjI1CmhhemVsIDwtIGRvLmNhbGwocmdiLCBhcy5saXN0KGhhemVsX3JnYiAvIDI1NSkpCgpjb2xzIDwtIGMoQmx1ZSA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oY29sb3JzcGFjZTo6ZGVzYXR1cmF0ZSgiYmx1ZSIsIDAuMyksIDAuMyksCiAgICAgICAgICBHcmVlbiA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oImZvcmVzdGdyZWVuIiwgMC4xKSwKICAgICAgICAgIEJyb3duID0gY29sb3JzcGFjZTo6bGlnaHRlbigiYnJvd24iLCAwLjAwMDEpLCAjIyAwLjM/CiAgICAgICAgICBIYXplbCA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oaGF6ZWwsIDAuMykpCgpwYiA8LSBnZ3Bsb3QoZXllKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEsIGZpbGwgPSBFeWUpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpCnBiCmBgYAoKQSBfc3RhY2tlZCBiYXIgY2hhcnRfIGNhbiBhbHNvIGJlIHVzZWZ1bDoKCmBgYHtyfQpwc2IgPC0gZ2dwbG90KGV5ZSkgKwogICAgZ2VvbV9jb2woYWVzKHggPSAiIiwgeSA9IEZyZXEsIGZpbGwgPSBFeWUpLCBjb2xvciA9ICJsaWdodGdyZXkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKQpwc2IKYGBgCgpBIF9waWUgY2hhcnRfIGNhbiBiZSBzZWVuIGFzIGEgc3RhY2tlZCBiYXIgY2hhcnQgaW4gcG9sYXIgY29vcmRpbmF0ZXM6CgpgYGB7cn0KKHBwIDwtIHBzYiArIGNvb3JkX3BvbGFyKCJ5IikpCmBgYAoKVGhlIGF4aXMgYW5kIGdyaWQgYXJlIG5vdCBoZWxwZnVsOyBhIF90aGVtZV8gYWRqdXN0bWVudCBjYW4gcmVtb3ZlIHRoZW06CgpgYGB7cn0KKHBwIDwtIHBwICsgdGhlbWVfdm9pZCgpKQpgYGAKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBUaGVtZXMgYXJlIGEgd2F5IHRvIGN1c3RvbWl6ZSB0aGUKbm9uLWRhdGEgY29tcG9uZW50cyBvZiBwbG90czogaS5lLiB0aXRsZXMsIGxhYmVscywgZm9udHMsIGJhY2tncm91bmQsCmdyaWRsaW5lcywgYW5kIGxlZ2VuZHMuIFRoZW1lcyBjYW4gYmUgdXNlZCB0byBnaXZlIHBsb3RzIGEgY29uc2lzdGVudApjdXN0b21pemVkIGxvb2suCgpUaGUgYGdndGhlbWVzYCBwYWNrYWdlIHByb3ZpZGVzIGEgbnVtYmVyIG9mIHRoZW1lcyB0byBlbXVsYXRlIHRoZQpzdHlsZSBvZiBkaWZmZXJlbnQgcHVibGljYXRpb25zLCBmb3IgZXhhbXBsZSBgdGhlbWVfd3NqYCBhbmQKYHRoZW1lX2Vjb25vbWlzdGAuICA8L2Rpdj4KCkhvdyB3ZWxsIGRvIGJhciBjaGFydHMgYW5kIHBpZSBjaGFydHMgd29yaz8KCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KY293cGxvdDo6cGxvdF9ncmlkKHBiLCBwcCkKYGBgCgpTb21lIHF1ZXN0aW9uczoKICAgICAKLSBXaGljaCBwbG90IG1ha2VzIGl0IGVhc2llciB0byB0ZWxsIHdoZXRoZXIgdGhlIHByb3BvcnRpb24gb2YKICBicm93bi1leWVkIHN0dWRlbnRzIGlzIGxhcmdlciBvciBzbWFsbGVyIHRoYXQgdGhlIHByb3BvcnRpb24gb2YKICBibHVlLWV5ZWQgc3R1ZGVudHMuCgotIFdoaWNoIHBsb3QgbWFrZXMgaXQgZWFzaWVyIHRvIHRlbGwgd2hldGhlciB0aGVzZSBwcm9wb3J0aW9ucyBhcmUKICBsYXJnZXIgb3Igc21hbGxlciB0aGFuIDEvMiBvciAxLzQgb3IgMS8zPwoKTG9va2luZyBhdCB0aGUgcHJvcG9ydGlvbnMgd2l0aGluIGhhaXIgY29sb3IgYW5kIHNleDoKICAgICAgCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KZXllX2hhaXJzZXggPC0gbXV0YXRlKGdyb3VwX2J5KEhhaXJFeWVERiwgSGFpciwgU2V4KSwgUHJvcCA9IEZyZXEgLyBzdW0oRnJlcSkpCnAxIDwtIGdncGxvdChleWVfaGFpcnNleCkgKwogICAgZ2VvbV9jb2woYWVzKHggPSBFeWUsIHkgPSBQcm9wLCBmaWxsID0gRXllKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29scykgKwogICAgZmFjZXRfZ3JpZChIYWlyflNleCkKcDIgPC0gZ2dwbG90KGV5ZV9oYWlyc2V4KSArCiAgICBnZW9tX2NvbChhZXMoeCA9ICIiLCB5ID0gUHJvcCwgZmlsbCA9IEV5ZSkpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpICsKICAgIGNvb3JkX3BvbGFyKCJ5IikrZmFjZXRfZ3JpZChIYWlyflNleCkgKwogICAgdGhlbWVfdm9pZCgpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwMSwgcDIpCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IEEgbW9yZSBjb21wbGV0ZSBgZ2dwbG90YCB0ZW1wbGF0ZToKCmBgYHIKZ2dwbG90KGRhdGEgPSA8REFUQT4pICsKICAgIDxHRU9NPihtYXBwaW5nID0gYWVzKDxNQVBQSU5HUz4pLAogICAgICAgICAgIHN0YXQgPSA8U1RBVD4sCiAgICAgICAgICAgcG9zaXRpb24gPSA8UE9TSVRJT04+KSArCiAgICA8IC4uLiBNT1JFIEdFT01TIC4uLiA+ICsKICAgIDxDT09SRElOQVRFX0FESlVTVE1FTlQ+ICsKICAgIDxTQ0FMRV9BREpVU1RNRU5UPiArCiAgICA8RkFDRVRJTkc+ICsKICAgIDxUSEVNRV9BREpVU1RNRU5UPgpgYGAKPC9kaXY+CgoKIyMgUGVyY2VwdGlvbiBhbmQgdGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MKCmBgYHtyfQpyaXZlciA8LSBzY2FuKCJkYXRhL3JpdmVyLmRhdCIpCnJkIDwtIGRhdGEuZnJhbWUoZmxvdyA9IHJpdmVyLCBtb250aCA9IHNlcV9hbG9uZyhyaXZlcikpCihwcCA8LSBnZ3Bsb3QocmQpICsgZ2VvbV9wb2ludChhZXMoeCA9IG1vbnRoLCB5ID0gZmxvdykpKQpgYGAKCmBgYHtyLCBldmFsID0gRkFMU0V9CihwbCA8LSBnZ3Bsb3QocmQpICsgZ2VvbV9saW5lKGFlcyh4ID0gbW9udGgsIHkgPSBmbG93KSkpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcHAgKyBjb29yZF9maXhlZCgzLjUpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcGwgKyBjb29yZF9maXhlZCgzLjUpCmBgYAoKIyMjIEEgU2ltcGxlIE1vZGVsIG9mIFZpc3VhbCBQZXJjZXB0aW9uCgpUaGUgZXllcyBhY3F1aXJlIGFuIGltYWdlLCB3aGljaCBpcyBwcm9jZXNzZWQgdGhyb3VnaCB0aHJlZSBzdGFnZXMgb2YKbWVtb3J5OgoKKiBJY29uaWMgbWVtb3J5CiogV29ya2luZyBtZW1vcnksIG9yIHNob3J0LXRlcm0gbWVtb3J5CiogTG9uZy10ZXJtIG1lbW9yeQoKVGhlIGZpcnN0IHByb2Nlc3Npbmcgc3RhZ2Ugb2YgYW4gaW1hZ2UgaGFwcGVucyBpbiBpY29uaWMgbWVtb3J5LgoKKiBJbWFnZXMgcmVtYWluIGluIGljb25pYyBtZW1vcnkgZm9yIGxlc3MgdGhhbiBhIHNlY29uZC4KKiBQcm9jZXNzaW5nIGluIGljb25pYyBtZW1vcnkgaXMgbWFzc2l2ZWx5IHBhcmFsbGVsIGFuZCBhdXRvbWF0aWMuCiogVGhpcyBpcyBjYWxsZWQgX3ByZWF0dGVudGl2ZSBwcm9jZXNzaW5nXy4KClByZWF0dGVudGl2ZSBwcm9jZXNzaW5nIGlzIGEgZmFzdCByZWNvZ25pdGlvbiBwcm9jZXNzLgoKTWVhbmluZ2Z1bCB2aXN1YWwgY2h1bmtzIGFyZSBtb3ZlZCBmcm9tIGljb25pYyBtZW1vcnkgdG8gc2hvcnQgdGVybSBtZW1vcnkuCgoqIFRoZXNlIGNodW5rcyBhcmUgdXNlZCBieSBjb25zY2lvdXMsIG9yIGF0dGVudGl2ZSwgcHJvY2Vzc2luZy4KKiBBdHRlbnRpdmUgcHJvY2Vzc2luZyBvZnRlbiBpbnZvbHZlcyBjb25zY2lvdXMgY29tcGFyaXNvbnMgb3Igc2VhcmNoLgoqIFNob3J0IHRlcm0gbWVtb3J5IGlzIGxpbWl0ZWQ7CiAgICAqIGluZm9ybWF0aW9uIGlzIHJldGFpbmVkIGZvciBvbmx5IGEgZmV3IHNlY29uZHM7CiAgICAqIG9ubHkgdGhyZWUgb3IgZm91cnMgY2h1bmtzIGNhbiBiZSBoZWxkIGF0IGEgdGltZS4KCkxvbmcgdGVybSB2aXN1YWwgbWVtb3J5IGlzIGJ1aWx0IHVwIG92ZXIgYSBsaWZldGltZSwgdGhvdWdoCmluZnJlcXVlbnRseSB1c2VkIHZpc3VhbCBjaHVua3MgbWF5IGJlY29tZSBsb3N0LgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+KipWaXN1YWwgRGVzaWduIEltcGxpY2F0aW9ucyoqCgoqIFRyeSB0byBtYWtlIGFzIG11Y2ggdXNlIG9mIHByZWF0dGVudGl2ZSBmZWF0dXJlcyBhcyBwb3NzaWJsZS4KCiogUmVjb2duaXplIHdoZW4gcHJlYXR0ZW50aXZlIGZlYXR1cmVzIG1pZ2h0IG1pc2xlYWQuCgoqIEZvciBmZWF0dXJlcyB0aGF0IHJlcXVpcmUgYXR0ZW50aXZlIHByb2Nlc3Npbmcga2VlcCBpbiBtaW5kIHRoYXQKICB3b3JraW5nIG1lbW9yeSBpcyBsaW1pdGVkLgo8L2Rpdj4KCiMjIyBTb21lIFRlcm1zIGZvciBEZXNjcmliaW5nIFZpc3VhbGl6YXRpb25zCgotIERhdGEgdG8gYmUgdmlzdWFsaXplZCBjb250YWlucyBfdmFyaWFibGVzXyBvciBfYXR0cmlidXRlc18gbWVhc3VyZWQKICBvbiBpbmRpdmlkdWFsIF9pdGVtc18gb3IgX2Nhc2VzXy4KCi0gX0xpbmtzXyBhcmUgcmVsYXRpb25zaGlwcyB0aGF0IG1heSBleGlzdCBhbW9uZyBpdGVtcywgZS5nLiBtb250aHMKICB3aXRoaW4gYSB5ZWFyIG9yIGNvdW50cmllcyB3aXRoaW4gYSBjb250aW5lbnQuCgotIF9NYXJrc18gYXJlIGluZGl2aWR1YWwgZ2VvbWV0cmljIGVudGl0aWVzIHVzZWQgdG8gcmVwcmVzZW50IGl0ZW1zOgogIHBvaW50cy4gYmFycywgZXRjLgoKLSBfQWVzdGhldGljc18gb3IgX3Zpc3VhbCBjaGFubmVsc18gYXJlIHRoZSB2aXN1YWwgZmVhdHVyZXMgb2YgbWFya3MKICB0aGF0IGNhbiBiZSB1c2VkIHRvIGVuY29kZSBhdHRyaWJ1dGVzLgoKVGhlIGBhZXMoLi4uKWAgZXhwcmVzc2lvbnMgZXN0YWJsaXNoIHRoZSBtYXBwaW5nIGJldHdlZW4gYXR0cmlidXRlcwphbmQgdmlzdWFsIGNoYW5uZWxzLgoKVGhlc2UgaWRlYXMgY2xvc2VseSBtaXJyb3IgdGhlIHN0cnVjdHVyZSBvZiB0aGUgX2dyYW1tYXIgb2YgZ3JhcGhpY3NfCmFzIGltcGxlbWVudGVkIGluIGBnZ3Bsb3RgLgoKCj4gTXVuem5lciwgVC4gKDIwMTQpLCBbX1Zpc3VhbGl6YXRpb24gQW5hbHlzaXMgYW5kCj4gIERlc2lnbl9dKGh0dHA6Ly93d3cuY3MudWJjLmNhL350bW0vdmFkYm9vay8pLCBDUkMgUHJlc3MuCgo+IFdpbGtpbnNvbiwgTC4gKDIwMDUpLCBfVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3NfLCAybmQgZWQsIFNwcmluZ2VyLgoKCiMjIyBDaGFubmVscyBhbmQgdGhlaXIgQWNjdXJhY3kKQSB1c2VmdWwgZGlzdGluY3Rpb24gYW1vbmcgY2hhbm5lbHM6CgotIF9NYWduaXR1ZGUgY2hhbm5lbHNfIGNhbiByZWZsZWN0IG9yZGVyIGFuZCBudW1lcmljIHZhbHVlcywKICBlLmcuIHBvc2l0aW9uIG9uIGFuIGF4aXMsIGxlbmd0aCwgYXJlYSwgYnJpZ2h0bmVzcy4KCi0gX0lkZW50aXR5IGNoYW5uZWxzXyBjYW4gZGlzdGluZ3Vpc2ggZGlmZmVyZW50IHZhbHVlcyBidXQgbm90IHJlZmxlY3QKICBvcmRlciwgZS5nLiBodWUsIHNoYXBlLCBncm91cGluZy4KClNvbWUgY2hhbm5lbHMgYXJlIGJldHRlciBhdCBjb252ZXlpbmcgaW5mb3JtYXRpb24gdGhhbiBvdGhlcnMuCgpNdW56bmVyJ3Mgb3JkZXJpbmcgYnkgYWNjdXJhY3k6CgpNYWduaXR1ZGUgQ2hhbm5lbHMgKE9yZGVyZWQsIE51bWVyaWNhbCkgICAgSWRlbnRpdHkgQ2hhbm5lbHMgKENhdGVnb3JpY2FsKQotLS0tLS0tLS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLS0tLS0KUG9zaXRpb24gb24gY29tbW9uIHNjYWxlICAgICAgICAgICAgICAgICAgIFNwYXRpYWwgZ3JvdXBpbmcKUG9zaXRpb24gb24gdW5hbGlnbmVkIHNjYWxlICAgICAgICAgICAgICAgIENvbG9yIGh1ZQpMZW5ndGggKDFEIHNpemUpICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hhcGUKVGlsdCwgYW5nbGUKQXJlYSAoMkQgc2l6ZSkKRGVwdGggKDNEIHBvc2l0aW9uKQpDb2xvciBsdW1pbmFuY2UsIHNhdHVyYXRpb24KQ3VydmF0dXJlLCB2b2x1bWUgKDNEIHNpemUpCgpMaW5lIHdpZHRoIGlzIGFub3RoZXIgY2hhbm5lbDsgbm90IHN1cmUgdGhlcmUgaXMgYWdyZWVtZW50IG9uIGl0cwphY2N1cmFjeSwgYnV0IGl0IGlzIG5vdCBoaWdoLgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+KipWaXN1YWwgRGVzaWduIEltcGxpY2F0aW9ucyoqCgpUcnkgdG8gbWFwIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXMgdG8gdGhlIHN0cm9uZ2VzdCBjaGFubmVscy4KPC9kaXY+CgoKIyMjIENvbG9yCgpDb2xvciBpcyB2ZXJ5IGVmZmVjdGl2ZSB3aGVuIHVzZWQgd2VsbC4KCkJ1dCB1c2luZyBjb2xvciB3ZWxsIGlzIG5vdCBlYXN5LgoKU29tZSBvZiB0aGUgaXNzdWVzOgoKLSBQZXJjZXB0aW9uIGRlcGVuZHMgb24gY29udGV4dC4KCi0gU2ltcGxlIGNvbG9yIGFzc2lnbm1lbnRzIG1heSBub3Qgc2VwYXJhdGUgZXF1YWxseSB3ZWxsLgoKLSBFZmZlY3RpdmVuZXNzIG1heSB2YXJ5IHdpdGggdGhlIG1lZGl1bSAoc2NyZWVuLCBwcm9qZWN0b3IsIHByaW50KS4KCi0gU29tZSBwZW9wbGUgZG8gbm90IHBlcmNlaXZlIHRoZSBmdWxsIHNwZWN0dXJtIG9mIGNvbG9ycy4KCi0gR3JleSBzY2FsZSBwcmludGluZy4KCi0gU29tZSBjb2xvcnMgaGF2ZSBjdWx0dXJhbCBzaWduaWZpY2FuY2UuCgotIEN1bHR1cmFsIHNpZ25pZmljYW5jZSBtYXkgdmFyeSBhbW9uZyBjdWx0dXJlcyBhbmQgd2l0aCB0aW1lLgoKQ29sb3IgcGVyY2VwdGlvbiBpcyByZWxhdGl2ZToKCiFbXShpbWcvY2hlc3MxLnBuZykgIVtdKGltZy9jaGVzczIucG5nKQoKQSBub3RlIG9uIFtyYWluYm93IGNvbG9yc10oCmh0dHBzOi8vZWVlY29uLnVpYmsuYWMuYXQvfnplaWxlaXMvbmV3cy9lbmRyYWluYm93LykuCgpTb21lIHRvb2xzIGZvciBzZWxlY3RpbmcgcGFsZXR0ZXMgaW5jbHVkZToKCi0gW0NvbG9yQnJld2VyXShodHRwOi8vY29sb3JicmV3ZXIyLm9yZyk7IGF2YWlsYWJsZSBpbiB0aGUKICBgUkNvbG9yQnJld2VyYCBwYWNrYWdlLgoKLSBbSENMIFdpemFyZF0oaHR0cDovL3d3dy5oY2x3aXphcmQub3JnLyk7IGFsc28gYXZhaWxhYmxlIGFzIGBoY2x3aXphcmRgCiAgaW4gdGhlIGBjb2xvcnNwYWNlYCBwYWNrYWdlLgoKCiMjIEEgR3JhbW1hciBvZiBEYXRhIE1hbmlwdWxhdGlvbgoKVGhlIGBkcGx5cmAgcGFja2FnZSBwcm92aWRlcyBhIGxhbmd1YWdlLCBvciBncmFtbWFyLCBmb3IgZGF0YQptYW5pcHVsYXRpb24uCgpUaGUgbGFuZ3VhZ2UgY29udGFpbnMgYSBudW1iZXIgb2YgX3ZlcmJzXyB0aGF0IG9wZXJhdGUgb24gdGFibGVzLgoKVGhlIG1vc3QgY29tbW9ubHkgdXNlZCB2ZXJicyBvcGVyYXRlIG9uIGEgc2luZ2xlIGRhdGEgZnJhbWU6CgoqIGBzZWxlY3RgOiBwaWNrIHZhcmlhYmxlcyBieSB0aGVpciBuYW1lcwoqIGBmaWx0ZXJgOiBjaG9vc2Ugcm93cyB0aGF0IHNhdGlzZnkgc29tZSBjcml0ZXJpYQoqIGBtdXRhdGVgOiBjcmVhdGUgdHJhbnNmb3JtZWQgb3IgZGVyaXZlZCB2YXJpYWJsZXMKKiBgYXJyYW5nZWA6IHJlb3JkZXIgdGhlIHJvd3MKKiBgc3VtbWFyaXplYDogY29sbGFwc2Ugcm93cyBkb3duIHRvIHN1bW1hcmllcwoKVGhlcmUgYXJlIGFsc28gYSBudW1iZXIgb2YgYGpvaW5gIHZlcmJzIHRoYXQgbWVyZ2Ugc2V2ZXJhbCBkYXRhIGZyYW1lcwppbnRvIG9uZS4KClRoZSBgdGlkeXJgIHBhY2thZ2UgcHJvdmlkZXMgYWRkaXRpb25hbCB2ZXJicywgc3VjaCBhcyBgZ2F0aGVyYCBhbmQKYHNwcmVhZGAgZm9yIHJlc2hhcGluZyBkYXRhIGZyYW1lcy4KClRoZSBzaW5nbGUgdGFibGUgdmVyYnMgY2FuIGFsc28gYmUgdXNlZCB3aXRoIGBncm91cF9ieWAgdG8gd29yayBhCmdyb3VwIGF0IGEgdGltZSBpbnN0ZWFkIG9mIGFwcGx5aW5nIHRvIHRoZSBlbnRpcmUgZGF0YSBmcmFtZS4KClRoZSBkZXNpZ24gb2YgYGRwbHlyYCBpcyBzdHJvbmdseSBtb3RpdmF0ZWQgYnkgU1FMLgoKCiMjIE1vcmUgRXhhbXBsZXMKClRoZXNlIGV4YW1wbGVzIHN0YXJ0IHdpdGggcmF3IGRhdGEgYXMgeW91IG1pZ2h0IHJlY2VpdmUgaXQgZnJvbSBhCnJlc2VhcmNoZXIgYW5kIGludm9sdmUgcmVhZGluZyBhbmQgY2xlYW5pbmcgdGhlIGRhdGEuCgoKIyMjIENhbmNlciBNYXAKClRoZSB3ZWJzaXRlIDxodHRwOi8vd3d3LmNhbmNlci1yYXRlcy5pbmZvL2lhPiBwcm92aWRlcyBkYXRhIG9uCmNhbmNlciBpbmNpZGVuY2UgZm9yIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBjYW5jZXJzIGluIElvd2EKVGhlIGRhdGEgZm9yIGx1bmcgYW5kIGJyb25jaHVzIGNhbmNlciBpbiAyMDExIGFyZSBhdmFpbGFibGUKaW4gYSBbY3N2IGZpbGVdKGRhdGEvSW52YXNpdmUtQ2FuY2VyLUluY2lkZW5jZS1SYXRlcy1ieS1Db3VudHktaW4tSW93YS1MdW5nLWFuZC1Ccm9uY2h1cy0yMDExLmNzdikgaW4gdGhlIHByb2plY3QuCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gW19DU1ZfIChjb21tYS1zZXBhcmF0ZWQKdmFsdWVzKV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ29tbWEtc2VwYXJhdGVkX3ZhbHVlcykgYXJlIGEKY29tbW9uIGZvcm0gb2YgZGF0YSBleGNoYW5nZS4gVGhleSBhcmUgc2ltcGxlIHRleHQgZmlsZXMgdGhhdCBhcmUKaW50ZW5kZWQgdG8gYmUgd3JpdHRlbiBhbmQgcmVhZCBieSBhIGNvbXB1dGVyLgoKVGhpcyBDU1YgZmlsZSBpcyB1bnVzdWFsIGluIHRoYXQgaXQgaW5jbHVkZXMgaGVhZGVyIGFuZCBmb290ZXIKaW5mb3JtYXRpb24gdGhhdCBjb21wbGljYXRlIG1hY2hpbmUgcmVhZGluZyBhIGJpdC4KCk9uZSBpc3N1ZSBpcyB0aGF0IGEgY29tbWEgaXNuJ3QgYSBnb29kIHNlcGFyYXRvciBpbiBjb3VudHJpZXMgd2hlcmUgaXQKaXMgdGhlIGRlY2ltYWwgc2VwYXJhdG9yISAgPC9kaXY+CgpXZSBjYW4gcmVhZCB0aGUgZmlsZSB3aXRoIGByZWFkX2NzdmAgZnJvbSB0aGUgYHJlYWRyYCBwYWNrYWdlLgoKTG9va2luZyBhdCB0aGUgZmlsZSBzaG93cyBzb21lIHRoaW5ncyB0aGF0IG5lZWQgdG8gYmUgY2xlYW5lZCB1cDoKCi0gVHdvIGhlYWRlciBsaW5lcyBhdCB0aGUgYmVnaW5uaW5nCi0gU29tZSBmb290ZXIgbGluZXMuCi0gU29tZSB2YWx1ZXMgY29kZXMgYXMgYH5gLgoKVGhlIGhlYWRlciBjYW4gYmUgaGFuZGxlZCBieSB1c2luZyBgc2tpcCA9IDJgIGluIHRoZSBgcmVhZF9jc3ZgIGNhbGw6CgpgYGB7cn0KZm5hbWUgPC0gImRhdGEvSW52YXNpdmUtQ2FuY2VyLUluY2lkZW5jZS1SYXRlcy1ieS1Db3VudHktaW4tSW93YS1MdW5nLWFuZC1Ccm9uY2h1cy0yMDExLmNzdiIKZCA8LSByZWFkX2NzdihmbmFtZSwgc2tpcCA9IDIpCmhlYWQoZCkKYGBgCgpMZXQncyBmb2N1cyBvbiBhIGZldyB2YXJpYWJsZXMgYW5kIGdpdmUgdGhlbSBtb3JlIGNvbnZlbmllbnQgbmFtZXM6CgpgYGB7cn0KZCA8LSBzZWxlY3QoZCwgY291bnR5ID0gMSwgcG9wdWxhdGlvbiA9IDIsIGNvdW50ID0gMywgY3J1ZGVfcmF0ZSA9IDQpCnRhaWwoZCkKYGBgCgpPbmUgd2F5IHRvIHJlbW92ZSB0aGUgZm9vdGVyOgoKYGBge3J9CmQgPC0gZmlsdGVyKGQsICEgaXMubmEocG9wdWxhdGlvbikpCnRhaWwoZCkKYGBgCgpDaGFuZ2luZyBgY291bnRgIGFuZCBgY3J1ZGVfcmF0ZWAgdG8gbnVtZXJpYyBjaGFuZ2VzIHRoZSBgfmAgZW50cmllcyB0byBtaXNzaW5nIHZhbHVlcyAoYE5BYCkgdmFsdWVzOgoKYGBge3J9CmQgPC0gbXV0YXRlKGQsIGNvdW50ID0gYXMubnVtZXJpYyhjb3VudCksIGNydWRlX3JhdGUgPSBhcy5udW1lcmljKGNydWRlX3JhdGUpKQpgYGAKCkluIHRoaXMgY2FzZSB0aGVyZSBhcmUgbm8gemVybyBjYXNlIHZhbHVlczsgdHdvIHdheXMgdG8gY2hlY2s6CgpgYGB7cn0KY291bnQoZCwgY291bnQgPT0gMCkKYW55KGQkY291bnQgPT0gMCwgbmEucm0gPSBUUlVFKQpgYGAKCkl0IF9taWdodF8gYmUgcmVhc29uYWJsZSB0byBhc3N1bWUgdGhlc2UgdmFsdWVzIHdoZXJlIHplcm8sIHNvIHJlcGxhY2UKdGhlbSB3aXRoIHplcm9zOgoKYGBge3J9CmQgPC0gcmVwbGFjZV9uYShkLCBsaXN0KGNvdW50ID0gMCwgY3J1ZGVfcmF0ZSA9IDApKQpgYGAKCkEgX2Nob3JvcGxldGggbWFwXyB1c2VzIGNvbG9yIG9yIHNoYWRpbmcgdG8gcmVwcmVzZW50IHZhbHVlcyBtZWFzdXJlZApmb3IgZGlmZmVyZW50IGdlb2dyYXBoaWMgcmVnaW9ucy4KCkluIHNpbXBsZSBjYXNlcywgbGlrZSBVUyBjb3VudGllcywgYSBjaG9yb3BsZXRoIG1hcCBjYW4gYmUgY3JlYXRlZCBieQpmaW5kaW5nIGRhdGEgZm9yIHBvbHlnb25zIGRlZmluaW5nIGNvdW50eSBib3JkZXJzIGFuZCBzaGFkaW5nIHRoZQpwb2x5Z29ucy4KClBvbHlnb24gZGF0YSBmb3IgVVMgY291bnRpZXMgaW4gdGhlIGxvd2VyIDQ4IHN0YXRlcyBjYW4gYmUgb2J0YWluZWQgd2l0aAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KbSA8LSBtYXBfZGF0YSgiY291bnR5IiwgImlvd2EiKQpoZWFkKG0pCm0gPC0gc2VsZWN0KG0sIC1yZWdpb24pCm0gPC0gcmVuYW1lKG0sIGNvdW50eSA9IHN1YnJlZ2lvbikKaGVhZChtKQpgYGAKCldlIHdpbGwgbmVlZCB0byBtZXJnZSwgb3IgX2xlZnQgam9pbl8sIHRoZSBkYXRhIHdlIHdhbnQgdG8gcGxvdCBpbnRvCnRoZSBwb2x5Z29uIGRhdGEgdXNpbmcgdGhlIGNvdW50eSBpZGVudGlmaWVyLgoKRm9yIElvd2EgdGhpcyBjYW4gYmUgZG9uZSB3aXRoIHRoZSBjb3VudHkgbmFtZSwgYnV0IHNvbWUgY2FyZSBpcyBuZWVkZWQuCgpgYGB7cn0KZCA8LSBtdXRhdGUoZCwgY25hbWUgPSBjb3VudHksIGNvdW50eSA9IHRvbG93ZXIoY291bnR5KSkKCnNldGRpZmYoZCRjb3VudHksIG0kY291bnR5KQpzZXRkaWZmKG0kY291bnR5LCBkJGNvdW50eSkKCmQgPC0gbXV0YXRlKGQsIGNvdW50eSA9IHN1YigiJyIsICIiLCBjb3VudHkpKQpkIDwtIGZpbHRlcihkLCBjb3VudHkgIT0gInN0YXRlIikKCnNldGRpZmYoZCRjb3VudHksIG0kY291bnR5KQpzZXRkaWZmKG0kY291bnR5LCBkJGNvdW50eSkKYGBgCgpEZWZpbmUgYHJhdGUxS2AgdmFyaWFibGUgYXMgdGhlIG51bWJlciBvZiBjYXNlcyBwZXIgMTAwMCBpbmhhYml0YW50cwphbmQgbGVmdCBqb2luIHRoZSBkYXRhIHRvIHRoZSBwb2x5Z29uczoKCmBgYHtyfQpkIDwtIG11dGF0ZShkLCByYXRlMUsgPSAxMDAwICogKGNvdW50IC8gcG9wdWxhdGlvbikpCm1kIDwtIGxlZnRfam9pbihtLCBkLCAiY291bnR5IikKaGVhZChtZCkKYGBgCgpBIHNpbXBsZSBtYXA6CgpgYGB7cn0KbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeSh2aXJpZGlzKQpnZ3Bsb3QobWQpICsKICAgIGdlb21fcG9seWdvbihhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXAsIGZpbGwgPSByYXRlMUspKQpgYGAKCkFuIGltcHJvdmVkIHZlcnNpb246CgpgYGB7cn0KbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeSh2aXJpZGlzKQpnZ3Bsb3QobWQpICsKICAgIGdlb21fcG9seWdvbihhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXAsCiAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSByYXRlMUspLAogICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXkiKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJSYXRlIHBlciAxMDAwIikgKwogICAgdGhlbWVfbWFwKCkgKyBjb29yZF9xdWlja21hcCgpCmBgYAoKQSBzaW1wbGUgaW50ZXJhY3RpdmUgdmVyc2lvbiB1c2luZyBbYHBsb3RseWBdKGh0dHBzOi8vcGxvdC5seS9yLyk6CgpgYGB7cn0KbWRsIDwtIG11dGF0ZShtZCwKICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlKGNuYW1lLCByb3VuZChyYXRlMUssIDEpLCBwb3B1bGF0aW9uLCBzZXAgPSAiXG4iKSkKcCA8LSBnZ3Bsb3QobWRsKSArCiAgICBnZW9tX3BvbHlnb24oYWVzKHggPSBsb25nLCB5ID0gbGF0LCBmaWxsID0gcmF0ZTFLLCBncm91cCA9IGdyb3VwLAogICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gbGFiZWwpLCAKICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5IikgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUmF0ZSBwZXIgMTAwMCIpICsKICAgIHRoZW1lX21hcCgpICsgY29vcmRfcXVpY2ttYXAoKQoKcGxvdGx5OjpnZ3Bsb3RseShwLCB0b29sdGlwID0gInRleHQiKQpgYGAKClRoZSBbYGxlYWZsZXRgXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvKSBwYWNrYWdlIHN1cHBvcnRzCm1vcmUgc29waGlzdGljYXRlZCBpbnRlcmFjdGl2ZSBtYXBzLgoKYGdlb21fbWFwYCBpcyBhbHRlcm5hdGl2ZSB0byBgZ2VvbV9wb2x5YCB0aGF0IGRvZXMgbm90IHJlcXVpcmUgdGhlCmpvaW4gb3BlcmF0aW9uIChpdCBpcyBkb25lIGludGVybmFsbHkpLiBJdCBjYW4gYmUgYSBiaXQgdHJpY2t5IHRvIHVzZQp0aG91Z2guCgkgICAKYGBge3J9CmdncGxvdChkLCBhZXMobWFwX2lkID0gY291bnR5LCBmaWxsID0gY291bnQvcG9wdWxhdGlvbikpICsKICAgIGdlb21fbWFwKG1hcCA9IHJlbmFtZShtLCBpZCA9IGNvdW50eSkgLCBjb2xvciA9ICJncmV5IikgKwogICAgd2l0aChtLCBleHBhbmRfbGltaXRzKHggPSBsb25nLCB5ID0gbGF0KSkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKCkgKwogICAgdGhlbWVfbWFwKCkgKyBjb29yZF9xdWlja21hcCgpCmBgYAoKRm9yIG1vcmUgY29tcGxleCBtYXBwaW5nIHByb2JsZW1zIGBnZW9tX3NmYCBpcyBhIGJldHRlciBjaG9pY2UuCgoKIyMjIFVuZW1wbG95bWVudCBNYXAKCltMb2NhbCBBcmVhIFVuZW1wbG95bWVudCBTdGF0aXN0aWNzIHBhZ2VdKGh0dHBzOi8vd3d3LmJscy5nb3YvbGF1LykKZnJvbSB0aGUgQnVyZWF1IG9mIExhYm9yIFN0YXRpc3RpY3MgbWFrZXMgYXZhaWxhYmxlIGNvdW50eS1sZXZlbAptb250aGx5IHVuZW1wbG95bWVudCBkYXRhIGZvciBhIDE0LW1vbnRoIHdpbmRvdy4gVGhlIGZpbGUgZm9yIE5vdmVtYmVyCjIwMTYgdGhyb3VnaCBEZWNlbWJlciAyMDE3IGlzIGF2YWlsYWJsZSBpcyBhdmFpbGFibGUgYXQKPGh0dHA6Ly93d3cuc3RhdC51aW93YS5lZHUvfmx1a2UvZGF0YS9sYXVzL2xhdWNudHljdXIxNC0yMDE3LnR4dD4KYW5kIGluIHRoZSBwcm9qZWN0IGRhdGEgZm9sZGVyLgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IFRoaXMgZmlsZSBpcyBhIHRleHQgZmlsZSBidXQgdXNlcyBhCm5vbi1zdGFuZGFyZCBzZXBhcmF0b3IuIEl0IGlzIGRlc2lnbmVkIGZvciBodW1hbiByZWFkYWJpbGl0eSBhbmQgdXNlcwphIGNvbW1hIGFzIGEgX3Rob3VzYW5kcyBzZXBhcmF0b3JfIG9yIF9ncm91cGluZyBtYXJrXy4gIEl0IGFsc28KaW5jbHVkZXMgaGVhZGVyIGFuZCBmb290ZXIgaW5mb3JtYXRpb24uIEl0IGlzIHN0aWxsIHJlYXNvbmFibHkgZWFzeSB0bwpyZWFkIGluLiAgPC9kaXY+CgpPbmUgd2F5IHRvIHJlYWQgdGhlIGRhdGEgaW50byBSIGlzOgoKYGBge3J9CmxhdXNVUkwgPC0gImRhdGEvbGF1Y250eWN1cjE0LTIwMTcudHh0IgpsYXVzVVMgPC0gcmVhZC50YWJsZShsYXVzVVJMLAogICAgICAgICAgICAgICAgICAgICBjb2wubmFtZXMgPSBjKCJMQVVTQXJlYUNvZGUiLCAiU3RhdGUiLCAiQ291bnR5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVGl0bGUiLCAiUGVyaW9kIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTGFib3JGb3JjZSIsICJFbXBsb3llZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlVuZW1wbG95ZWQiLCAiVW5lbXBSYXRlIiksCiAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gJyInLCBzZXAgPSAifCIsIHNraXAgPSA2LAogICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsIHN0cmlwLndoaXRlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFRSVUUpCmZvb3RzdGFydCA8LSBncmVwKCItLS0tLS0iLCBsYXVzVVMkTEFVU0FyZWFDb2RlKQpsYXVzVVMgPC0gbGF1c1VTWzE6KGZvb3RzdGFydCAtIDEpLF0KYGBgCgpJdCBtYXkgYmUgdXNlZnVsIHRvIGJlIGFibGUgdG8gYWNjZXNzIHRoZSBjb3VudHkgbmFtZSBhbmQgc3RhdGUgbmFtZQpzZXBhcmF0ZWx5OgoKYGBge3J9CmxhdXNVUyA8LSBzZXBhcmF0ZShsYXVzVVMsIFRpdGxlLCBjKCJjbmFtZSIsICJzY29kZSIpLAogICAgICAgICAgICAgICAgICAgc2VwID0gIiwgIiwgZmlsbCA9ICJyaWdodCIpCmBgYAoKVGhlIGBVbmVtcFJhdGVgIHZhcmlhYmxlIGlzIHJlYWQgYXMgY2hhcmFjdGVyIGRhdGEgYmVjYXVzZSBvZiBtaXNzaW5nCnZhbHVlIGVuY29kaW5nLCBzbyBuZWVkcyB0byBiZSBjb252ZXJ0ZWQgdG8gbnVtZXJpYzoKCmBgYHtyfQpsYXVzVVMgPC0gbXV0YXRlKGxhdXNVUywgVW5lbXBSYXRlID0gYXMubnVtZXJpYyhVbmVtcFJhdGUpKQpgYGAKCkNoZWNrIGZvciBtaXNzaW5nIHZhbHVlczoKCmBgYHtyfQpzYXBwbHkobGF1c1VTLCBmdW5jdGlvbih4KSBhbnkoaXMubmEoeCkpKQpgYGAKClRoZSBzdGF0ZSBjb2RlIGlzIG1pc3NpbmcgZm9yIHRoZSBEaXN0cmljdCBvZiBDb2x1bWJpYToKCmBgYHtyfQp1bmlxdWUoZmlsdGVyKHNlbGVjdChsYXVzVVMsIGNuYW1lLCBzY29kZSksIGlzLm5hKHNjb2RlKSkpCmBgYAoKTWlzc2luZyB2YWx1ZXMgZm9yIGBVbmVtcFJhdGVgIGFyZSBhbGwgZm9yIFB1ZXJ0byBSaWNvIGFuZCBTZXB0ZW1iZXIKMjAxNy4gSHVycmljYW5lIE1hcmlhIG1hZGUgbGFuZGZhbGwgb24gU2VwdGVtYmVyIDIwLgoKYGBge3J9CnVuaXF1ZShmaWx0ZXIoc2VsZWN0KGxhdXNVUywgc2NvZGUsIFBlcmlvZCwgVW5lbXBSYXRlKSwgaXMubmEoVW5lbXBSYXRlKSkpCmBgYAoKQXZlcmFnZSB1bmVtcGxveW1lbnQgcmF0ZXMgb3ZlciB0aGUgcGVyaW9kIGNhbiBiZSBjb21wdXRlZCBhcwoKYGBge3J9CmF2Z1VTIDwtIHN1bW1hcml6ZShncm91cF9ieShsYXVzVVMsIENvdW50eSwgU3RhdGUpLAogICAgICAgICAgICAgICAgICAgYXZnX3VuZW1wID0gbWVhbihVbmVtcFJhdGUpLAogICAgICAgICAgICAgICAgICAgY25hbWUgPSB1bmlxdWUoY25hbWUpLAogICAgICAgICAgICAgICAgICAgc2NvZGUgPSB1bmlxdWUoc2NvZGUpKQpoZWFkKGF2Z1VTKQpgYGAKClRvIHNob3cgYXZlcmFnZSB1bmVtcGxveW1lbnQgcmF0ZXMgb24gYSBtYXAgd2UgbmVlZCB0byBtZXJnZSB0aGUKdW5lbXBsb3ltZW50IGRhdGEgd2l0aCBtYXAgZGF0YS4KCkl0IGlzIHNhZmVyIHRvIHVzZSB0aGUgbnVtZXJpYyBbRklQUyBjb3VudHkKY29kZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRklQU19jb3VudHlfY29kZSksIHdoaWNoIGNhbiBiZQpjb21wdXRlZCBhcwoKQWRkIEZJUFMgY29kZSB0byBgYXZnVVNgOgoKYGBge3J9CmF2Z1VTIDwtIG11dGF0ZShhdmdVUywgZmlwcyA9IDEwMDAgKiBTdGF0ZSArIENvdW50eSkKaGVhZChhdmdVUykKYGBgCgpUaGUgYGNvdW50eS5maXBzYCBkYXRhIGZyYW1lIGluIHRoZSBgbWFwc2AgcGFja2FnZSBsaW5rcyB0aGUgRklQUyBjb2RlCnRvIHJlZ2lvbiBuYW1lcyB1c2VkIGJ5IHRoZSBtYXAgZGF0YSBpbiB0aGUgYG1hcHNgIHBhY2thZ2UuCgpgYGB7cn0KbGlicmFyeShtYXBzKQpoZWFkKGNvdW50eS5maXBzKQpgYGAKClNvbWUgaXNzdWVzOgoKYGBge3J9CmZpbHRlcihjb3VudHkuZmlwcywgZ3JlcGwoImZsb3JpZGEsbyIsIHBvbHluYW1lKSkKaGVhZChzZWxlY3QoZmlsdGVyKGxhdXNVUywgc2NvZGUgPT0gIkxBIiksIGNuYW1lKSkKYGBgCgpDbGVhbmluZyB1cCBhIGJpdDoKCmBgYHtyfQpjb3VudHkuZmlwcyA8LSBzZXBhcmF0ZShjb3VudHkuZmlwcywgcG9seW5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgIGMoInN0YXRlIiwgImNvdW50eSIsICJwYXJ0IiksCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJbLDpdIiwgZmlsbCA9ICJyaWdodCIpCmhlYWQoY291bnR5LmZpcHMpCmBgYAoKQ291bnR5IG1hcCBkYXRhIGZvciB0aGUgbG93ZXIgNDggc3RhdGVzOgoKYGBge3J9CmNvdW50aWVzX1VTIDwtIG1hcF9kYXRhKCJjb3VudHkiKQpjb3VudGllc19VUyA8LSByZW5hbWUoY291bnRpZXNfVVMsIHN0YXRlID0gcmVnaW9uLCBjb3VudHkgPSBzdWJyZWdpb24pCmNvdW50aWVzX1VTIDwtIGxlZnRfam9pbihjb3VudGllc19VUywgY291bnR5LmZpcHMsIGMoInN0YXRlIiwgImNvdW50eSIpKQpgYGAKCkEgY2hvcm9wbGV0aCBtYXA6CgpgYGB7cn0KZ2dwbG90KGxlZnRfam9pbihjb3VudGllc19VUywgYXZnVVMsICJmaXBzIikpICsKICAgIGdlb21fcG9seWdvbihhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGZpbGwgPSBhdmdfdW5lbXAsIGdyb3VwID0gZ3JvdXApKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJSYXRlIiwgbmEudmFsdWUgPSAicmVkIikgKwogICAgdGhlbWVfbWFwKCkgKyBjb29yZF9tYXAoKSArIAogICAgZ2VvbV9wb2x5Z29uKGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksCiAgICAgICAgICAgICAgICAgZGF0YSA9IG1hcF9kYXRhKCJzdGF0ZSIpLCBjb2wgPSAiZ3JleSIsIGZpbGwgPSBOQSkKYGBgCgpBIHZlcnNpb24gdXNpbmcgYGdlb21fbWFwYCBsZWF2ZXMgb3V0IFNoYW5ub24gQ291bnR5LCBTRCAoNDYxMTMpIHdoaWNoCmZvciBzb21lIHJlYXNvbiBpcyBub3QgaW4gdGhlIExBVVMgZGF0YS4KCjwhLS0gQ2hlY2sgaG93IHRvIHVzZSBnZW9tX3NmPyAtLT4KCmBgYHtyfQpnZ3Bsb3QoYXZnVVMsIGFlcyhmaWxsID0gYXZnX3VuZW1wLCBtYXBfaWQgPSBmaXBzKSkgKwogICAgZ2VvbV9tYXAobWFwID0gbXV0YXRlKGNvdW50aWVzX1VTLCBpZCA9IGZpcHMpKSArCiAgICB3aXRoKGNvdW50aWVzX1VTLCBleHBhbmRfbGltaXRzKHggPSBsb25nLCB5ID0gbGF0KSkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUmF0ZSIsIG5hLnZhbHVlID0gInJlZCIpICsKICAgIHRoZW1lX21hcCgpICsgY29vcmRfbWFwKCkKYGBgCgogICAKIyMjIEdhcG1pbmRlciBDaGlsZGhvb2QgTW9ydGFsaXR5IERhdGEKClRoZSBgZ2FwbWluZGVyYCBwYWNrYWdlIHByb3ZpZGVzIGEgc3Vic2V0IG9mIHRoZSBkYXRhIGZyb20gdGhlCltHYXBtaW5kZXJdKGh0dHA6Ly93d3cuZ2FwbWluZGVyLm9yZy8pIHdlYiBzaXRlLiBBZGRpdGlvbmFsIGRhdGEgc2V0cwphcmUgW2F2YWlsYWJsZV0oaHR0cDovL3d3dy5nYXBtaW5kZXIub3JnL2RhdGEvKS4KCi0gQSBkYXRhIHNldCBvbiBjaGlsZGhvb2QgbW9ydGFsaXR5IGlzIGF2YWlsYWJsZSBsb2NhbGx5IGFzIGEgW2NzdgogIGZpbGVdKGh0dHA6Ly9ob21lcGFnZS5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL2dhcG1pbmRlci11bmRlcjVtb3J0YWxpdHkuY3N2KQogIG9yIGFuIFtFeGNlbAogIGZpbGVdKGh0dHA6Ly9ob21lcGFnZS5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL2dhcG1pbmRlci11bmRlcjVtb3J0YWxpdHkueGxzeCkuIFRoZQogIEV4Y2VsIGZpbGUgaXMgYWxzbyBhdmFpbGFibGUgaW4gdGhlIHByb2plY3QgZGF0YSBmb2xkZXIuCgotIFRoZSBudW1iZXJzIHJlcHJlc2VudCBudW1iZXIgb2YgZGVhdGhzIHdpdGhpbiB0aGUgZmlyc3QgZml2ZSB5ZWFycwogIHBlciAxMDAwIGJpcnRocy4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBNYW55IHJlc2VhcmNoZXJzIGxpa2UgdG8gbWFuYWdlIHRoZWlyCmRhdGEgaW4gYSBzcHJlYWRzaGVldC4gQmVpbmcgYWJsZSB0byByZWFkIHN1Y2ggYSBzaGVldCBkaXJlY3RseQpncmVhdGx5IGhlbHBzIGtlZXBpbmcgdGhlIHdvcmtmbG93IHJlcHJvZHVjaWJsZS4KCk1hbnkgc3ByZWFkc2hlZXRzIGNvbnRhaW4gaGVhZGVyLCBmb290ZXJzLCBhbmQgb3RoZXIgYW5ub3RhdGlvbnMgdG8KYWlkIGEgaHVtYW4gdmlld2VyLgoKQXMgbG9uZyBhcyB0aGUgZGF0YSBhcmUgaW4gYSByZWN0YW5ndWxhciByZWdpb24gaXQgaXMgdXN1YWxseSBub3QgaGFyZAp0byBleHRyYWN0IHRoZW0gcHJvZ3JhbW1hdGljYWxseS4gIDwvZGl2PgoKTG9hZGluZyB0aGUgZGF0YToKCmBgYHtyfQpsaWJyYXJ5KHJlYWR4bCkKZ2NtIDwtIHJlYWRfZXhjZWwoImRhdGEvZ2FwbWluZGVyLXVuZGVyNW1vcnRhbGl0eS54bHN4IikKbmFtZXMoZ2NtKVsxXQpuYW1lcyhnY20pWzFdIDwtICJjb3VudHJ5IgpgYGAKClRoaXMgZGF0YSBzZXQgaXMgaW4gX3dpZGVfIGZvcm1hdC4KCkEgX2xvbmdfIHZlcnNpb24gaXMgdXNlZnVsIGZvciB3b3JraW5nIHdpdGggYGdncGxvdGAuCgpgYGB7cn0KdGdjbSA8LSBnYXRoZXIoZ2NtLCB5ZWFyLCB1NW1vcnQsIC0xKQpoZWFkKHRnY20pCnRnY20gPC0gbXV0YXRlKHRnY20sIHllYXIgPSBhcy5udW1lcmljKHllYXIpKQpoZWFkKHRnY20pCmBgYAoKClNvbWUgZXhwbG9yYXRpb25zOgpgYGB7cn0KbGlicmFyeShsYXR0aWNlKQpwIDwtIGdncGxvdCh0Z2NtKSArIGdlb21fbGluZShhZXMoeWVhciwgdTVtb3J0LCBncm91cCA9IGNvdW50cnkpLCBhbHBoYSA9IDAuMykKcApwbG90bHk6OmdncGxvdGx5KHApCmBgYAoKU29tZSBzZWxlY3RlZCBjb3VudHJpZXM6CgpgYGB7cn0KY291bnRyaWVzIDwtIGMoIlVuaXRlZCBTdGF0ZXMiLCAiVW5pdGVkIEtpbmdkb20iLCAiR2VybWFueSIsICJDaGluYSIsICJFZ3lwdCIpCnRjZ20xIDwtIGZpbHRlcih0Z2NtLCBjb3VudHJ5ICVpbiUgY291bnRyaWVzKQpnZ3Bsb3QodGNnbTEpICsgZ2VvbV9saW5lKGFlcyh4ID0geWVhciwgeSA9IHU1bW9ydCwgY29sb3IgPSBjb3VudHJ5KSkKYGBgCgpFeGFtaW5pbmcgdGhlIG1pc3NpbmcgdmFsdWVzOgoKYGBge3J9CnRnY21fbWlzcyA8LSBzdW1tYXJpemUoZ3JvdXBfYnkodGdjbSwgY291bnRyeSksIGFueU5BID0gYW55KGlzLm5hKHU1bW9ydCkpKQp0Z2NtX21pc3MgPC0gZmlsdGVyKHRnY21fbWlzcywgYW55TkEpJGNvdW50cnkKcCA8LSBnZ3Bsb3QoZmlsdGVyKHRnY20sIGNvdW50cnkgJWluJSB0Z2NtX21pc3MpKSArCiAgICBnZW9tX2xpbmUoYWVzKHggPSB5ZWFyLCB5ID0gdTVtb3J0LCBncm91cCA9IGNvdW50cnkpLCBuYS5ybSA9IFRSVUUpCnAKcGxvdGx5OjpnZ3Bsb3RseShwKQpgYGAKCgoKPCEtLQpMb2NhbCBWYXJpYWJsZXM6IAptb2RlOiBwb2x5LW1hcmtkb3duK1IKbW9kZTogZmx5c3BlbGwKRW5kOgotLT4KCg==